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.
Related
I'm using Django 1.11.2 to develop a website. I use ModelForms to edit my model instances on my website. Every field of the form gets the fitting value of the instance I want to edit via 'initial' in my view. It works fine for all fields except ManyToManyFields.
The relevant code looks like this:
models.py:
class model1(models.Model):
name = models.CharField(max_length=45, blank=False, null=False)
class model2(models.Model):
name = models.CharField(max_length=45, blank=False, null=False)
relation = models.ManyToManyField(model1)
the ModelForm in forms.py:
class model2_form(forms.ModelForm):
class Meta:
model = model2
fields = '__all__'
and the view I use to edit model2 intances:
def model2_edit(request, objectid):
link = 'Model2'
model2_inst = model2.objects.get(id=objectid)
form = model2_form(initial={'name': model2_inst.name,
'relation': ???})
if request.method == 'POST':
f = model2_form(request.POST, instance=model2_inst)
if f.is_valid():
f.save()
return HttpResponseRedirect('/model2')
return render(request, "edit_db.html",
{"form": form, "link":link})
Everytime I edit an instance of model2 via the ModelForm, the 'relations' of the instance that already exist aren't preselected ('initial' isn't working). If I save the form like this without selecting the relations again, they get deleted and that instance of model2 has no relations anymore.
At the place of the '???' in my code I tried many ways to get those relations already selected in the form, but I couldn't find a working way.
I hope I managed to describe my problem, thanks in advance for any help or ideas.
form = model2_form(initial={'name': model2_inst.name,
'relation': [i.id for i in model2_inst.relation.all()]})
You should provide the instance for GET and POST requests. This way, you do not need to provide initial data - Django will get the values from the instance automatically.
model2_inst = model2.objects.get(id=objectid)
form = model2_form(instance=model2_inst)
I have come to an impasse when using a ModelForm.
I'm extending the User model that comes with Django, and I'm also using a ModelForm so the user can edit it.
Following the same example in the documentation, I would have this code.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# In this case, department is optional, so I have set 'blank' and 'null' to True.
department = models.CharField(max_length=100, blank=True, null=True)
forms.py
class DepartmentForm(ModelForm):
class Meta:
model = Employee
fields = ['department',]
The problem comes at the view. I found that I need to pass an instance of the model to the form so the save() function works without having to customize it, but of course, user.employee has not been created yet, therefore it throws an error.
views.py
def DepartmentView(request):
# Here is the issue.
department = request.user.employee
if request.method == 'POST':
# I need to pass the instance here.
form = DepartmentForm(request.POST, instance=department)
if form.is_valid():
form.save()
else:
# And also here so it autocompletes the form.
form = DepartmentForm(instance=department)
return render(request, 'employee.html', {'form': form})
It works if I manually add a value to user.employee.department through the shell and then reload the page, otherwise the error is as follow.
RelatedObjectDoesNotExist at [something]
User has no employee.
Or something like that... I'm sorry, I didn't try the code above so the error could be a little different, but the concept is exactly the same.
I'm also sorry if this has been asked before. I did a Google search and couldn't find an answer to this issue.
You could use get_or_create to fetch the employee from the db, or create it if it doesn't exist.
department, created = Employee.objects.get_or_create(user=request_or_user, department='')
if request.method == 'POST':
form = DepartmentForm(request.POST, instance=department)
...
Another option is to use a signal, so that the related model is created when the user is created. Then you can assume that the employee already exists, and you can use request.user.employee instead of get_or_create.
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.
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.
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.