Saving Django ModelForms with Multiple Inheritance - python

I have a web application that allows users to create an account, and in doing so creates a user object form a the standard Django User model, associated with a custom UserProfile model, as well as an Address model. I have built an HTML form that allows the user to update their address, and profile, by means of using a ContactInfoForm that subclasses both the AddressForm and UserProfileForm; both of which are ModelForms, as follows:
class AddressForm(forms.ModelForm):
class Meta:
model = common_models.Address
exclude = ('updated_dt','address_type','created_dt')
def __init__(self,*args,**kwargs):
super(AddressForm,self).__init__(*args,**kwargs)
firstname = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Enter First Name'})
lastname = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Enter Last Name'})
address1 = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Enter Address'})
etc...
class UserProfileForm(forms.ModelForm):
class Meta:
model = common_models.UserProfile
exclude = ('created_dt','updated_dt','entity_active','profile_hash','user','address')
account_type = forms.ChoiceField(choices=account_choices,widget=forms.Select(attrs={'id':'account_type_list'}),error_messages={'required':'Please Select Account Type'})
name = forms.CharField(max_length=100, min_length=1, error_messages={'required':'Please Company Name'})
supplier_type = forms.ModelChoiceField(queryset=common_models.SupplierTypeCode.objects.all(),required=False,widget=forms.Select(attrs={'id':'account_type_select'}))
buyer_type = forms.ModelChoiceField(queryset=common_models.ClientTypeCode.objects.all(),widget=forms.Select(attrs={'id':'account_type_select'}),required=False)
class ContactInfoForm(AddressForm,UserProfileForm):
class Meta:
model = common_models.User
exclude = ('email','username',
'password','last_login','date_joined')
def __init__(self,user=None,request_post_data=None,*args,**kwargs):
if not request_post_data:
params = dict([tuple([k,v]) for k,v in user.get_profile().address.__dict__.items()] +
[tuple([k,v]) for k,v in user.get_profile().__dict__.items()])
super(ContactInfoForm,self).__init__(initial=params,*args,**kwargs)
else:
super(ContactInfoForm,self).__init__(request_post_data,instance=user)
Now, I have the following questions:
1) How do I save the ContactInfoForm, such that both the user_profile and the address tables are updated, along with the auth_user table? I have tried overriding the save function in the ContactInfoForm, then calling the save function of Address and UserProfile as follows:
def save(self):
address = AddressForm.save(self)
profile = UserProfileForm.save(self)
however, that doesn't work as the instance of self is a user object, and thus both the above functions return a user object
2) Is my implementation of the init method of the ContactInfoForm the best way to pre-populate the HTML form when the user first visits the update contact info page? In other words, is the construction of the params dictionary and using it as the initial argument correct. Keep in my mind, I have access to the user object from request.user since this view is behind a login_required decorator...
3) Is there perhaps a better way to achieve what I am trying to achieve that isn't as complicated and more Django/Pythonic?

Usually in Django such thing is being made by creating 3 separate forms and process them all in one view.
address_form = AddressForm(request.POST)
profile_form = UserProfileForm(request.POST)
contacts_form = ContactInfoForm(request.POST)
if address_form.is_valid() and profile_form.is_valid() and contacts_form.is_valid():
address_form.save()
profile_form.save()
contacts_form.save()
Maybe it's bit more code this way but it's much more clear and easy to read.

Related

How to avoid this redundancy when using Django model inheritance?

All pages in my Django website have a footer link "Feedback/Questions". If the new person comes to the site and clicks that link, they should be directed to a form with a pulldown to indicate if they have feedback versus a question and fields for their email address and their feedback or question. The page will have a simple header all non-authenticated users will see. On the other hand, if a site member signs in and is authenticated, they should see the same form but without the email field (since I already know their email address) and a different authenticated header containing the site's internal navbar, buttons, etc.
My initial thought was to create an abstract class FeedbackQuestion:
class FeedbackQuestion(models.Model):
submission_type = ... (type, i.e. feedback or question)
submission_text = ... (actual feedback or question)
...
class Meta:
abstract = True
Then I'd create two separate concrete child classes:
class AnonFeedbackQuestion(FeedbackQuestion):
email = models.EmailField(...)
class Meta:
db_table = anon_feedback_question
class AuthFeedbackQuestion(FeedbackQuestion):
user = models.ForeignKey(User, related_name="user")
class Meta:
db_table = auth_feedback_question
These two classes would have their own model forms:
class AnonFeedbackQuestionForm(ModelForm):
class Meta:
model = AnonFeedbackQuestion
fields = ['submission_type', 'submission_text', 'email']
class AuthFeedbackQuestionForm(ModelForm):
class Meta:
model = AuthFeedbackQuestion
fields = ['submission_type', 'submission_text']
The problem I forsee is that I will have to do the following in my view that displays the feedback form:
def get_feedback_questions(request, template):
if request.method == 'POST':
...
if request.user.is_authenticated():
form = AuthFeedbackQuestionForm(request.POST)
else:
form = AnonFeedbackQuestionForm(request.POST)
if form.is_valid():
(process form)
...
else:
if request.user.is_authenticated():
form = AuthFeedbackQuestionForm(request.POST)
else:
form = AnonFeedbackQuestionForm(request.POST)
...
context = {'form': form}
return render(request, template, context)
Having to repeat these if/then/else blocks to identify which form to use seems rather inelegant. Is there a better, cleaner "Django" way to do this?
Thanks!
I wouldn't subclass your models - if it's an anonymous question you could just include a user attribute as well as an email attribute on one model with blank=True and null=True:
class FeedbackQuestion(models.Model):
submission_type = ... (type, i.e. feedback or question)
submission_text = ... (actual feedback or question)
email = models.EmailField(..., blank=True, null=True)
user = models.ForeignKey(User, related_name="user", blank=True, null=True)
...
class Meta:
abstract = True
This way you can add either the email for an anonymous user's feedback/question or the user if they're authenticated.
Then I'd combine your forms into one including the email field, but remove the email field depending on if the user is authenticated (see this answer):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(UserForm, self).__init__(*args, **kwargs)
if self.user:
# For logged-in users, email field not necessary
self.fields.pop('email')
else:
# Otherwise, the field needs to be required
self.fields['email'].required = True
Then you just need to make sure you create the user appropriately as you clean the form's data (e.g., make sure the email address isn't already taken, etc.)

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Saving form with m2m relationship not saving to database

I am trying to save a multiple select check box form using a m2m structure, but my values are not being saved to the database.
I have a State and Options. A State can have multiple Options and Options can have multiple states. In practice, I want to save multiple options for each state and then save the connections in the intermediate StateOption table. There is no error produced but when I check my database, nothing has been saved.
Also, if you see anything wrong with the way I set up my database structure, feel free to comment. I am new to databases and django.
models.py
class Option(models.Model):
relevantdisease = models.ForeignKey(Disease)
option = models.CharField(max_length=255)
class State(models.Model):
state = models.CharField(max_length=255)
relevantdisease = models.ForeignKey(Disease)
relevantoption = models.ManyToManyField(Option, blank=True, through='StateOption')
#intermediate table may not be needed
class StateOption(models.Model):
state_table = models.ForeignKey(State)
option_table = models.ForeignKey(Option)
forms.py
class StateOptionForm(forms.ModelForm):
option_choices = forms.ModelMultipleChoiceField(queryset=Option.objects.all(), required=False, widget=forms.CheckboxSelectMultiple)
class Meta:
model = State #StateOption if I use the intermediate table
exclude = ['state_table', 'option_table']
views.py
def stateoption(request, disease_id, state_id):
state = get_object_or_404(State, pk=state_id)
disease = get_object_or_404(Disease, pk=disease_id)
if request.method == "POST":
form = StateOptionForm(request.POST, instance=state)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save() #this and the line below is probably where the problem is
form.save_m2m()
#stateoption = StateOption.objects.create(state_table=state, option_table=profile) <--produces an error saying that the instance needs to be Option
return HttpResponseRedirect(reverse('success'))
else:
form = StateOptionForm(instance=state)
context = {'state': state, 'disease':disease, 'option': form }
return render(request, "stateoption.html", context)
Update
An intermediate table is probably not needed for this use case, but it will be needed as I add more complexity to this problem. Is there a way to save this form to the database with an intermediate table?
Solve it by changing:
class StateOptionForm(forms.ModelForm):
option_choices = forms.ModelMultipleChoiceField(queryset=Option.objects.all(), required=False, widget=forms.CheckboxSelectMultiple)
to class StateOptionForm(forms.ModelForm):
relevantoption = forms.ModelMultipleChoiceField(queryset=Option.objects.all(), required=False, widget=forms.CheckboxSelectMultiple)
so that it matches my model.

How can I prefill value in a custom form field in a ModelForm

Let's say I have a model as follows.
models.py
class Profile(models.Model):
user = models.OneToOneField(User)
middle_name = models.CharField(max_length=30, blank=True, null=True)
And I have a custom field email as follows in a ModelForm
forms.py
class ProfileForm(ModelForm):
email = forms.CharField()
class Meta:
model = models.Profile
fields = ('email', 'middle_name')
In the am setting an instance of the above mentioned modelform so the data is prefilled in the form for an edit template as follows.
views.py
def edit_profile(request):
profile = models.Profile.objects.get(user=request.user)
profileform = forms.ProfileForm(instance=profile)
return render_to_response('edit.html', { 'form' : 'profileform' }, context_instance=RequestContext(request))
Now in the form I get all the values prefilled for all the fields under Profile model but the custom fields are empty and it makes sense.
but is there a way I can prefill the value of the custom fields ? maybe something like:
email = forms.CharField(value = models.Profile.user.email)
Can I suggest something else? I'm not a huge fan of having that email field within a ModelForm of Profile if it has nothing to do with that model.
Instead, how about just having two forms and passing in initial data to your custom one containing email? So things would look like this:
forms.py
# this name may not fit your needs if you have more fields, but you get the idea
class UserEmailForm(forms.Form):
email = forms.CharField()
views.py
profile = models.Profile.objects.get(user=request.user)
profileform = forms.ProfileForm(instance=profile)
user_emailform = forms.UserEmailForm(initial={'email': profile.user.email})
Then, you're validating both the profile and user email form, but otherwise things are mostly the same.
I assume you are not sharing logic between the Profile ModelForm and this UserEmailForm. If you need profile instance data, you could always pass that in there.
I prefer this approach because it's less magical and if you look back at your code in a year, you won't be wondering why, in brief scanning, why email is part of the ModelForm when it does not exist as a field on that model.

When should I use user.get_profile in django?

I saw another answer here and other places on the web that recommend using user.get_profile when extending the built-in django user. I didn't do that in the below example. The functionality seems to be working fine, but is there a downside for not using user.get_profile()?
model
class UserProfile(models.Model):
user = models.ForeignKey(User, primary_key=True)
quote = models.CharField('Favorite quote', max_length = 200, null=True, blank=True)
website = models.URLField('Personal website/blog', null=True, blank=True)
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
fields = ('quote', 'website')
view
#login_required
def user_profile(request):
user = User.objects.get(pk=request.user.id)
if request.method == 'POST':
upform = UserProfileForm(request.POST)
if upform.is_valid():
up = upform.save(commit=False)
up.user = request.user
up.save()
return HttpResponseRedirect('/accounts/profile')
else:
upform = UserProfileForm()
return render_to_response('reserve/templates/edit_profile.html', locals(), context_instance=RequestContext(request))
The code works as you've written it, but because you don't pass an instance to your model it's a bit unusual, so it might take another Django developer a bit longer to work out what's going on.
The view you link to instantiates the model form with an instance, so that the existing profile values are displayed in the form. In your case, you'll get empty fields.
upform = UserProfileForm(instance=user.get_profile())
Because you don't provide an instance, saving would try to create a new user_profile, which we wouldn't want. That won't happen in your case, because you've made user the primary key, but that's a little unusual as well.
The main advantage of writing user.get_profile() is that you don't need to know which model is used for the user profile. If you are happy to hardcode UserProfile model in your code, you could put instance=UserProfile.objects.get(user=user) instead.

Categories

Resources