make some model field conditionally visible in django - django

I have two field in my django model they should be editable only if user have selected 'type' as 'Dimention' otherwise they should not be visible to user.
My model is look like this code
from django.db import models
class Configuration(models.Model):
name = models.CharField(max_length=30)
user_defined_name = models.CharField(max_length=50)
FieldTypes = (('aD', 'Dimension'), ('aM', 'Measure'))
type = models.CharField(max_length=11, choices=FieldTypes)
is_key = models.BooleanField(default=False, editable=False)
unit = models.CharField(max_length=30, null=True, blank=True, editable=False)
def __str__(self):
return self.name
I know it is possible by using JavaScript but, I don't want to write html or js myself,Thus Can't use JavaScript for doing this.

A pure Django-only way to achieve this is to simply reset the fields from your ModelForm if type is not equal to Dimension. This will appear like magic/unintended behavior; so be careful of the implementation.
For example (assuming you are using the admin interface: the same is valid for a custom ModelForm View):
class ConfigurationAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# At this point; the object already has the new values set; we will have to reset as needed
conditional_editable_fields = ['is_key', 'unit']
config_type = form.cleaned_data.get('type')
if config_type != 'aD':
for field in conditional_editable_fields:
if field in form.changed_data: # The value has been changed by the user
setattr(obj, field, form.initial.get(field)) # Set the initial value
self.message_user(request, "Cannot edit field: {}; value has been reset".format(field), messages.WARNING) # Inform the user that we reset the value
return super(ConfigurationAdmin, self).save_mode(request, obj, form, change)

Related

Disable form field in Django's CreateView

This question may also be formulated as: "How to change attributes of a CreateView generated form?"
I am using "CreateView" to generate a view and related form in Django 1.10. The idea is that a regular user (a teacher) can create an instance of the model only as himself/herself but a privileged user can create an instance and assign to any teacher. I would like to use the same view/form for both.
The model:
class Set(models.Model):
name = models.CharField(
max_length=40,
)
matter = models.ForeignKey(
Matter,
on_delete=models.SET_NULL,
null=True,
)
group = models.ForeignKey(
Group,
on_delete=models.SET_NULL,
null=True,
)
teacher = models.ForeignKey(
Teacher,
on_delete=models.PROTECT,
)
Technically, it would consist on populating the form with the current "teacher" logged in and disable the field unless the current user is privileged. I currently do the initial value setting with the following code, but I do not know how to prevent regular users from modifying the "teacher" field.
class SetCreate(LoginRequiredMixin, CreateView):
model = Set
fields = ('name', 'matter', 'group', 'teacher')
def get_initial(self):
return {'teacher': self.request.user.teacher.id}
# code to limit 'teacher' field editing
Another option I tried is to create the instance with the right 'teacher', like:
class SetCreate(LoginRequiredMixin, CreateView):
model = Set
fields = ('name', 'matter', 'group')
def form_valid(self, form):
form.instance.teacher = self.request.user.teacher
return super(SetCreate, self).form_valid(form)
It works nice but I cannot use it to edit the 'teacher' field by a privileged user.
I know there exists the 'Field.disable' attribute to form fields, but I do not know how to change the attributes of 'CreateView' generated forms, if possible.
Another option would be to limit the options in the drop-down list in the form, but I do not know how to do it either.
Any advice including a different point of view is welcome.
Looking at the code, the editing views generate a form within get_form_class based on the value of the fields attribute. That is defined as a class attribute, but the code actually references it via self.fields so there is no reason you couldn't override it at instance level. So, you could do this:
class SetCreate(LoginRequiredMixin, CreateView):
model = Set
fields = ('name', 'matter', 'group')
def get_form_class(self, *args, **kwargs):
if self.request.user.has_the_right_permission():
self.fields += ('teacher',)
else:
self.fields = self.fields
return super(SetCreate, self).get_form_kwargs(*args, **kwargs)
(The seemingly pointless else block is to ensure we always set an instance variable.)

Django admin - how can I use save_related() to parse data from inline forms?

I need to intercept when the user saves related (inline) data in django admin to call an external API.
I have the following models:
class Hostel(models.Model):
name = models.CharField(max_length=50)
descr = models.CharField(max_length=200)
....
class User(models.Model):
name = models.CharField(max_length=50, blank=False)
nick = models.CharField(max_length=20, blank=False)
password = models.CharField(max_length=20, blank=False)
class HostelGuest(models.Model):
hostel = models.ForeignKey(Hostel)
guest = models.ForeignKey(User)
entry_date = models.DateTimeField(null=True,blank=True)
class HostelEntryPoint(models.Model):
hostel = models.ForeignKey(Hostel)
getting_here = models.CharField(max_length=300, blank=False)
distance = models.CharField(max_length = 30, blank = True)
In admin.py I have in the same page Hostel as parent and HostelGuest and EntryPoint as children. Whenever some data in HostelGuest changes, I need to find out if it's add/delete/update and call an external API. I thought of using save_related() but I'm unable to parse the data received and figure out the action taken. This is as far as I got, any help will be much appreciated!
class HostelEntryPointInline(admin.TabularInline):
model = HostelEntryPoint
extra = 3
class HostelGuestInline(admin.TabularInline):
model=HostelGuest
extra=4
class HostelAdmin(admin.ModelAdmin):
inlines = [HostelGuestInline, HostelEntryPointInline]
def save_related(self, request, form, formsets, change):
super(HostelAdmin, self).save_related(request, form, formsets, change)
logger = logging.getLogger(__name__)
obj = form.instance
obj.save()
for formset in formsets:
for f in formset:
logger.error('form name:' + f.__class__.__name__)
logger.error('form:' + f.cleaned_data['guest'])
admin.site.register(Hostel,HostelAdmin)
you can use Form in your admin.TabularInline.
this is example.
class HostelGuestForm(forms.ModelForm):
# set your code.
class HostelGuestInline(admin.TabularInline):
model=HostelGuest
form = HostelGuestForm
extra=4
if you want call external API, this is one of solutions.
you should type on your class HostelAdmin
def save_formset(self, request, form, formset, change):
if formset == formset_factory(HostelGuestForm):
# this `if` is check for formset is for HostelGuest
instances = formset.save(commit=False)
# it returns your added, changed, deleted instance
# if you don't have to check that instance is added or changed or deleted, below code is not required.
for instance in instances:
if instance.pk == None:
# this is added
elif:
# check `change` for is changed or deleted
formset.save_m2m()
else:
pass
the def save_formset() is called by save_related(), and the reason about not change save_related, change save_formset is for other inline form. because inline formset can possible for be added not only HostelGuest.

Extending Django default user

I'm trying to build a user profile which builds on the default Django user. I'm trying to extend the default user with an extra fields like this:
class MyMember(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL) #not sure about this
birthday = models.DateField()
USERNAME_FIELD = 'user' #how to use username field from default user?
REQUIRED_FIELDS = ['birthday',]
def __unicode__(self):
return self.username
But I'm getting this error: 'Manager' object has no attribute 'get_by_natural_key'? Why?
I would also like the USERNAME_FIELD to be the username from the default user. How?
Here's the UserProfile model class I use to extend the User model in my demo website:
#Private function required by image field.
def _get_upload_file_name_w_sub_dir(instance, filename):
return "uploaded_files/{0}{1}_{2}".format("profile_pic/", (str(time()).replace(".", "_"), filename))
class UserProfile(models.Model):
# This line is required. Links UserProfile to a User model instance.
# related_name is so you can reference *this* model as "user.profile"
# instead of "user.userprofile"
user = models.OneToOneField(User, related_name="profile")
# The additional attributes we wish to include.
year_discovered = models.IntegerField(blank=True,
verbose_name="Year you discovered Billy Joel's music/became a fan")
profile_picture = models.ImageField(upload_to=get_upload_profile_pic_file_name,
blank=True, null=True)
# Override the __unicode__() method to return out something meaningful!
def __unicode__(self):
return self.user.username
Here is the official documentation on extending the user model.
This is how you would extend the default Django User model. You would want to use a ForeignKey, and then you can use dot notation to access the fields of the User model.
Here:
from django.contrib.auth.models import User
class MyMember(models.Model):
user = models.ForeignKey(User)
birthday = models.DateField()
def __unicode__(self):
# the default "username" field in the django user model
return self.user.username

Django - How ModelChoiceField queryset's works?

I have a form with a ModelChoiceField, and i want to load on it a table from my DB. If i use this queryset on my form's init, then my view's form.is_valid works fine:
self.fields['categoria_formfield'].queryset = sitio_categoria.objects.exclude(categoria='patrimonio').values_list('idCategoria',flat=True)
That code show a id's list on the ModelChoiceField, but what i need it's to show the categories list. So i use:
self.fields['categoria_formfield'].queryset = sitio_categoria.objects.exclude(categoria='patrimonio').values_list('categoria',flat=True)
But using this code .is_valid don't validate and i recive a form error: "Select a valid choice. That choice is not one of the avaliable choices." Some clue about what could be the problem?
MODEL
class sitio_categoria(models.Model):
idCategoria = models.AutoField(primary_key=True)
categoria = models.CharField(max_length=30, null=False, unique=True)
FORM
class anadirComercioPaso1_form(forms.Form):
categoria_formfield = forms.ModelChoiceField(widget=forms.Select(attrs={'size':'13', 'onchange':'this.form.action=this.form.submit()'}), queryset=sitio_categoria.objects.none())
def __init__(self, *args, **kwargs):
super(anadirComercioPaso1_form, self).__init__(*args,**kwargs)
self.fields['categoria_formfield'].queryset = sitio_categoria.objects.exclude(categoria='patrimonio').values_list('categoria',flat=True)
Do not use values_list, (or values), ModelChoiceField needs actual model objects.
queryset = sitio_categoria.objects.exclude(categoria='patrimonio')
ModelChoiceField will use the primary keys of the objects for validation and their unicode representation for displaying. So you will need to define the conversion to unicode in your model:
class sitio_categoria(models.Model):
idCategoria = models.AutoField(primary_key=True)
categoria = models.CharField(max_length=30, null=False, unique=True)
def __unicode__(self):
return self.categoria
ModelChoiceField documentation
The __unicode__ method of the model will be called to generate string representations of the objects for use in the field’s choices;

Displaying the function's name in the search fields in Django Admin

I would like to display the function name in search fields in Django Admin interface but when i do it
The models.py:
class Adult(models.Model):
user = models.OneToOneField(User)
firstname = models.CharField(max_length=100,
blank=True)
lastname = models.CharField(max_length=100,
blank=True)
def __unicode__(self):
return self.user.username
def Parent_Name(self):
try:
return '%s %s' % (adult.firstname,adult.lastname)
except Exception:
return ''
getParentName.shot_description = 'adult'
Admin.py:
class AdultAdmin(admin.ModelAdmin):
list_display = ('Parent_Name', 'Student_Name',)
search_fields = ['Parent_Name',]
admin.site.register(Adult, AdultAdmin)
Error:
Cannot resolve keyword 'Parent_Name' into field.
What should be the correct way of writing this so that the function name can be used for search_fields?
The search_fields should be:
... set to a list of field names that will be searched whenever somebody submits a search query in that text box.
You can't specify your own search function, as it only accepts a list of strings indicating the fields you want to search (unlike the list_display option that you have mentioned which does accept functions - allowing you to programmatically return the value of the list item).
You can see the appropriate code that deals with search_fields here (and it's limitations) on github. The best you can do is follow relationships.

Resources