I'm new to Django and I'm trying to implement a project where companies can register as users. As the companies may come from different countries and have different corporate forms they have some different mandatory fields when registering. The differences are only informational and have no consequence on the behaviour, i.e. all users should be treated equally after the registration. At the moment I only need to support 2 countries and 2 corporate forms but maybe more should be supported in the future.
I started with a single company model in form of a user-profile (one-to-one relationship with user) but in that case I have to set the fields which are different between company forms and countries as nullable and handle the correct input in the forms?
Furthermore I want to see all users (companies) in one single list in the admin.
How would you model that in Django?
Django form is very flexible in fields manipulation. You could always add/remove required from a field, or even delete a field from a form on the fly. All you need is to pass a parameter to the form constructor __init__(). Here's a basic example:
class BlahForm(forms.ModelForm):
class Meta:
model = Blah
fields = ('field1', 'field2',)
def __init__(self, *args, **kwargs):
param = kwargs.pop('param', None)
super(BlahForm, self).__init__(*args, **kwargs)
# how to mark a field as "not required"
if param == 'not_required':
self.fields['field1'].required = False
# here's how to delete a field
elif param == 'delete_field':
del self.fields['field1']
In the views.py you can initialize your form like this:
if 'condition1':
form = BlahForm(request.POST or None, param='not_required')
elif 'condition2':
form = BlahForm(required.POST or None, param='delete_field')
Your form would behave depends on that parameter you passed in, thus you only need one form for all countries(as you said the differences are trivial). Hope this helps.
Related
I'm making a settings interface which works by scanning for a settings folder in the installed applications, scanning for settings files, and finally scanning for ModelForms.
I'm at the last step now. The forms are properly found and loaded, but I now need to provide the initial data. The initial data is to be pulled from the database, and, as you can imagine, it must be limited to the authenticated user (via request.user.id).
Keep in mind, this is all done dynamically. None of the names for anything, nor their structure is known in advanced (I really don't want to maintain a boring settings interface).
Here is an example settings form. I just pick the model and which fields the user can edit (this is the extent to which I want to maintain a settings interface).
class Set_Personal_Info(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('nick_name', 'url')
I've looked at modelformset_factory which almost does what I want to do, but it only seems to work with results of two or more. (Here, obj is one of the settings forms)
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.filter(id=request.user.id))
I can't filter the data, I have to get one, and only one result. Unfortunately I can't use get()
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.get(id=request.user.id))
'User' object has no attribute 'ordered'
Providing the query result as initial data also doesn't work as it's not a list.
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(initial=obj.Meta.model.objects.get(id=request.user.id))
'User' object does not support indexing
I have a feeling that the answer is right in front of me. How can I pull database from the database and shove it into the form as initial values?
I'm not really sure I understand what you're trying to do - if you're just interested in a single form, I don't know why you're getting involved in formsets at all.
To populate a modelform with initial data from the database, you just pass the instance argument:
my_form = Set_Personal_Info(instance=UserProfile.objects.get(id=request.user.id))
Don't forget to also pass the instance argument when you're instantiating the form on POST, so that Django updates the existing instance rather than creating a new one.
(Note you might want to think about giving better names to your objects. obj usually describes a model instance, rather than a form, for which form would be a better name. And form classes should follow PEP8, and probably include the word 'form' - so PersonalInfoForm would be a good name.)
Based on what I've understand ... if you want to generate a form with dynamic fields you can use this:
class MyModelForm(forms.ModelForm):
def __init__(self, dynamic_fields, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields = fields_for_model(self._meta.model, dynamic_fields, self._meta.exclude, self._meta.widgets)
class Meta:
model = MyModel
Where dynamic_fields is a tuple.
More on dynamic forms:
http://www.rossp.org/blog/2008/dec/15/modelforms/
http://jacobian.org/writing/dynamic-form-generation/
http://dougalmatthews.com/articles/2009/dec/16/nicer-dynamic-forms-django/
Also Daniel's approach is valid and clean ... Based on your different ids/types etc you can you use different Form objects
forms.py
class MyModelFormA(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_a','field_b','field_c')
class MyModelFormB(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_d','field_e','field_f')
views.py
if request.method == 'POST':
if id == 1:
form = MyModelFormA(data=request.POST)
elif id == 2:
form = MyModelFormB(data=request.POST)
else:
form = MyModelFormN(data=request.POST)
if form.is_valid():
form.save() else:
if id == 1:
form = MyModelFormA()
elif id == 2:
form = MyModelFormB()
else:
form = MyModelFormN()
I'd like to create a form that when viewed, the user's favorite fruits are queried from the database and displayed as follows:
<select size="4">
<option selected>Apples</option>
<option>Bananas</option>
<option>Oranges</option>
<option>Watermelon</option>
</select>
The view that uses the form will:
Get the user object.
Query the database for the user's favorite fruits. (Each is a separate object of the Fruit model.)
Load the form with the fruit choices collected in (2).
I was considering using the ChoiceField, but it looks like you cannot load the list of choices into the form dynamically, at least in a straightforward manner. Am I better off skipping the form and generating the code directly at the template? Or is there a way to load the form's ChoiceField with the user items at the view?
Also, are there any general rules of thumb that dictate where it's easier to build a form using the django form fields vs generating the form code at the template?
I found the answer in this stack overflow topic. The trick is to override the form __init__() so that it accepts a new keyword argument, which in this case is the user.
views.py snippet
context = RequestContext(request)
user = User.objects.get(username=context['user'])
form = forms.FruitForm(user=user)
forms.py snippet
from django import forms
class FruitForm(forms.Form):
fruits = forms.ModelChoiceField(queryset=Fruit.objects.all())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(FruitForm, self).__init__(*args, **kwargs)
if user:
self.fields['fruits'].queryset = Fruit.objects.filter(user=user)
It's not that difficult. You can accomplish this easily using a modelform.
See: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
One of the strengths of the Django framework is it's form handling and validation methods. So if possible, it always better for you to use Django forms or model forms.
Create a Form or a ModelForm that will be used in you view. The differnce between the two classes is the the ModelForm is built to closely resemble a database model defined in your models.py file where a Form can have custom attributes.
from django.forms import ModelForm
class FruitForm(ModelForm):
class Meta:
model = User
fields = ('favorite-fruits', )
I have a Django application with users. I have a model called "Course" with a foreign key called "teacher" to the default User model that Django provides:
class Course(models.Model):
...
teacher = models.ForeignKey(User, related_name='courses_taught')
When I create a model form to edit information for individual courses, the possible users for the teacher field appear in this long select menu of user names.
These users are ordered by ID, which is of meager use to me. How can I
order these users by their last names?
change the string representation of the User class to be "Firstname Lastname (username)" instead of "username"?
Firsty, the order. You can define a default order of model using the nested Meta class. Check the ordering section.
Secondly, representation. You have to define a __str__()/__unicode__() methods for your model. They should return a string which represents an object. You can see an example in documentation. BUT, since User is a model from an outer module it may be hard to do it in that way.
You probably can:
monkey-patch the User model meta class during app initialization OR
subclass the User model, add Meta to the subclass and use it in place of the User OR
write a custom field / form template which uses objects attributes instead of calling str()
it all depends on the current case
Abstract
If you're only dealing with the admin, it's better not to tinker with the User model itself, as subclassing User can be a pain down the road (especially when / if other developpers are going to work on your project), but use the admin's customization options.
Solution
To solve your issue in the admin, you could use the ModelAdmin option raw_id_fields.
This will replace the <select> input with a widget that you can click to be redirected to your User admin and choose your user from.
From there, it's trivial to customize your User admin so that it:
Displays your users in a relevant order (ModelAdmin.ordering)
Displays the fields you're interest in (ModelAdmin.list_display)
Just remember than when you're registering your User admin, you need to use:
admin.site.unregister(User) #This!
admin.site.register(User, MyCustomUserAdmin)
Alternate solution
Alternatively, you can always use ModelAdmin.formfield_overrides, which is more powerful but more complicated.
Reference
You should look into the Django documentation for details on how to use those attributes.
You could achieve this by customizing form field, here the forms.ModelChoiceField
class CustomizedModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
# change default ordering
super(CustomizedModelChoiceField, self).__init__(*args, **kwargs)
self.queryset = self.queryset.order_by('last_name')
def label_from_instance(self, obj):
# change representation per item
return u'{obj.first_name} {obj.last_name} ({obj.username})'.format(obj=obj)
Then use it in your form to replace default ModelChoiceField.
In Django Admin, it looks like
class CourseAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'teacher':
kwargs['form_class'] = CustomizedModelChoiceField
return super(CourseAdmin, self).formfield_for_dbfield(db_field, **kwargs)
I currently have a model set up like so:
I have a Test class with a foreign key to a custom UserProfile class. This property is called student. So each UserProfile may have taken more than one Test. Then in turn, each UserProfile is tied to a User by the typical onetoone relation. Another level of complexity is added because I have declared two user groups tutors and students. And the UserProfile class has a ManyToMany relation on to itself labeled "tutors". The logic is that each student may have many tutors teaching him. Now what I would like to do is add filters on the right-hand side of the Test admin page that lets you filter by the student and filter by tutor as well. setting list_filter = ('student',) simply lists all available UserProfiles. But this would include the UserProfiles which are tied to tutors as well. Obviously I would like to filter this list down to only students, because clearly filtering by any of these tutors would result in an empty queryset. I would then like to do a similar thing with filtering by tutor, where in this case the shorthand would be list_filter = ('student__tutors'), but I'd want this UserProfile set to be filtered down to only include those where user_groups_name='tutors'. What is the best way to do this?
For clarity, my object model looks like this:
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True,related_name='profile')
tutors = models.ManyToManyField("self",related_name="students")
class Test(models.Model):
student = models.ForeignKey(UserProfile,related_name='tests')
I tried subclassing django.contrib.admin.SimpleListFilter with the following:
class StudentListFilter(SimpleListFilter):
title = 'student'
parameter_name = 'student__id__exact'
def lookups(self, request, model_admin):
qs = model_admin.queryset(request)
return User.objects.filter(profile__id__in=qs.values_list('student__id')).extra(select={'full_name':'first_name||" "||last_name'}).values_list('profile__id','full_name')
def queryset(self, request, queryset):
if self.value() is not None:
return queryset.filter(student__id__exact=self.value())
else:
return queryset
Which seems to work for the first list_filter, but the trouble is for some reason there's a bug now where the selected option does not get highlighted in the custom filter. Only 'All' highlights but not the custom options. Here is an example of it that another user posted:
http://imgur.com/lyrYk
I am currently using the development version of django 1.4, so I'm not sure if this issue is tied to that or not.
Interesting problem.
I think you need to convert your lookups to a string.
return [(str(x), y) for x, y in your_valuesqueryset]
Line 98 on django.admin.filters defines the selected filter as:
'selected': self.value() == lookup
Where the lookup is populated directly from the results of the lookups method.
The auto type coercion in your filter() call is making the filter succeed but '2' != 2
Given a model with ForeignKeyField (FKF) or ManyToManyField (MTMF) fields with a foreignkey to 'self' how can I prevent self (recursive) selection within the Django Admin (admin).
In short, it should be possible to prevent self (recursive) selection of a model instance in the admin. This applies when editing existing instances of a model, not creating new instances.
For example, take the following model for an article in a news app;
class Article(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField()
related_articles = models.ManyToManyField('self')
If there are 3 Article instances (title: a1-3), when editing an existing Article instance via the admin the related_articles field is represented by default by a html (multiple)select box which provides a list of ALL articles (Article.objects.all()). The user should only see and be able to select Article instances other than itself, e.g. When editing Article a1, related_articles available to select = a2, a3.
I can currently see 3 potential to ways to do this, in order of decreasing preference;
Provide a way to set the queryset providing available choices in the admin form field for the related_articles (via an exclude query filter, e.g. Article.objects.filter(~Q(id__iexact=self.id)) to exclude the current instance being edited from the list of related_articles a user can see and select from. Creation/setting of the queryset to use could occur within the constructor (__init__) of a custom Article ModelForm, or, via some kind of dynamic limit_choices_to Model option. This would require a way to grab the instance being edited to use for filtering.
Override the save_model function of the Article Model or ModelAdmin class to check for and remove itself from the related_articles before saving the instance. This still means that admin users can see and select all articles including the instance being edited (for existing articles).
Filter out self references when required for use outside the admin, e.g. templates.
The ideal solution (1) is currently possible to do via custom model forms outside of the admin as it's possible to pass in a filtered queryset variable for the instance being edited to the model form constructor. Question is, can you get at the Article instance, i.e. 'self' being edited the admin before the form is created to do the same thing.
It could be I am going about this the wrong way, but if your allowed to define a FKF / MTMF to the same model then there should be a way to have the admin - do the right thing - and prevent a user from selecting itself by excluding it in the list of available choices.
Note: Solution 2 and 3 are possible to do now and are provided to try and avoid getting these as answers, ideally i'd like to get an answer to solution 1.
Carl is correct, here's a cut and paste code sample that would go in admin.py
I find navigating the Django relationships can be tricky if you don't have a solid grasp, and a living example can be worth 1000 time more than a "go read this" (not that you don't need to understand what is happening).
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['myManyToManyField'].queryset = MyModel.objects.exclude(
id__exact=self.instance.id)
You can use a custom ModelForm in the admin (by setting the "form" attribute of your ModelAdmin subclass). So you do it the same way in the admin as you would anywhere else.
You can also override the get_form method of the ModelAdmin like so:
def get_form(self, request, obj=None, **kwargs):
"""
Modify the fields in the form that are self-referential by
removing self instance from queryset
"""
form = super().get_form(request, obj=None, **kwargs)
# obj won't exist yet for create page
if obj:
# Finds fieldnames of related fields whose model is self
rmself_fields = [f.name for f in self.model._meta.get_fields() if (
f.concrete and f.is_relation and f.related_model is self.model)]
for fieldname in rmself_fields:
form.base_fields[fieldname]._queryset =
form.base_fields[fieldname]._queryset.exclude(id=obj.id)
return form
Note that this is a on-size-fits-all solution that automatically finds self-referencing model fields and removes self from all of them :-)
I like the solution of checking at save() time:
def save(self, *args, **kwargs):
# call full_clean() that in turn will call clean()
self.full_clean()
return super().save(*args, **kwargs)
def clean(self):
obj = self
parents = set()
while obj is not None:
if obj in parents:
raise ValidationError('Loop error', code='infinite_loop')
parents.add(obj)
obj = obj.parent