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.
Related
I'm building a page that allows users to edit Task and related Activity records (one task can have many activities), all on the same page. I want to allow the user to "adopt" one or more activities by ticking a box, and have their user record linked to each activity via a ForeignKey. Here are extracts from my code...
models.py
from django.contrib.auth.models import User
class Task(models.Model):
category = models.CharField(max_length=300)
description = models.CharField(max_length=300)
class Activity(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
notes = models.TextField(blank=True)
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
The activity "owner" is linked to a User from the Django standard user model.
I added an extra field in the form definition for the adopt field - I don't want to add it to the model as I don't need to save it once it's done it's job.
forms.py
class ActivityForm(forms.ModelForm):
adopt = forms.BooleanField(required=False)
class Meta:
model = Activity
fields = '__all__'
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
## ? DO SOMETHING HERE ? ##
formset.save()
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
I just can't figure out how to do that - if it was a single form (rather than an InlineFormSet), I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this). Or try save(commit = False) and update the model instance then save() it.
Maybe I have to iterate through the formset in the view code and try one of those options when I find one that had adopt=True?
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
formset = TaskInlineFormSet(request.POST, instance = task)
if formset.adopt:
# If True
formset.user = request.user
formset.save()
I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this).
You should give it a try.
I'm not happy with this solution but it does work. I iterate through the forms and change the object instance if my adopt field is set.
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
## HERE'S WHAT I ADDED ##
for form in formset:
if form.cleaned_data['adopt'] is True:
form.instance.owner = request.user
## END OF ADDITIONS ##
formset.save()
## return redirect('manage_task',pk=task.id) # CHANGED THIS BECAUSE I WASN'T RETURNG ERRORS!
if not form.errors and not formset.total_error_count():
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
I wish I could find more in the docs about how the form saving works but I think I'll have to look into the code if I want more detail.
I spent a lot of hours searching for a feature which I think should be quite a basic functionality in Django. But I just can't get it working,
I am unable to find a widget which will function same as m2m widget of django, but will also create new model instance if it doesn't exists.
Note: Here model instance already exists means that data entered in inline widget already exists in database.
E.g.
If I had models as:
class Outcome(models.Model):
outcome = models.CharField(max_length=255)
outcome_short_name = models.CharField(max_length=10, blank=True, null=True)
class Course(models.Model):
course_title = models.CharField(
verbose_name=COURSE_SINGULAR + " title", max_length=200, unique=True
)
course_outcome = models.ManyToManyField(
Outcome, verbose_name=COURSE_SINGULAR + " outcome", blank=True
)
Then I want "Outcomes" shown as this image while creating course:
Image of adding new course with inline outcomes
Now, If the outcomes data added by user already exists, then it should only map them to course. Otherwise it should first store outcomes into database and then map them to course.
Any guidance in right direction will be highly appreciated.
Thanks,
EDIT:
As suggested by #dirkgroten to use modelformset, I changed my FormView as:
class CourseFormView(FormView):
template_name = "course/course_form.html"
form_class = CourseForm
success_url = "/admin/"
def get_context_data(self, **kwargs):
context = super(CourseFormView, self).get_context_data(**kwargs)
if self.request.POST:
context["outcomes"] = OutcomeFormSet(self.request.POST)
else:
context["outcomes"] = OutcomeFormSet(queryset=Outcome.objects.none())
return context
def form_valid(self, form, **kwargs):
super(CourseFormView, self).get_context_data(**kwargs)
context = self.get_context_data()
outcomes_formset = context["outcomes"]
if not outcomes_formset.is_valid():
return super().form_invalid(form)
cleaned_data = form.cleaned_data
cleaned_data.pop("course_outcome")
course = Course.objects.create(**cleaned_data)
course.save()
outcomes_formset.instance = course
outcomes_formset.save()
course.course_outcome.set(Outcome.objects.filter(course_outcome=course))
return super().form_valid(form)
Everything looks fine except my model_formset is not validated if form data in formset already exists in database.
E.g. if I enter (outcome="test_outcome", outcome_short_name="test_short") in formset and same data already exists in outcome table, then my formset gives error:
Outcome with this Outcome and Outcome short name already exists.
Is there any way to tackle this situation or I am doing something wrong.
You can test above at: http://code.gdy.club:8001/course/add/
outcomes_list: http://code.gdy.club:8001/outcome/
Thanks,
--
Suraj
https://hacksj4u.wordpress.com
https://github.com/SurajDadral
You need to handle the case where an Outcome already exists yourself. The default when validating the form is to assume a new object will be created, so if your fields are set to be unique_together, then the individual form will not validate.
You could do it on the OutcomeFormset's clean() method like this:
from django.core.exceptions import NON_FIELD_ERRORS
def clean(self):
super().clean()
for i in range(0, self.total_form_count()):
form = self.forms[i]
if form.non_field_errors() and len(form.errors) == 1:
# the only error is probably due to unique_together constraint
try:
form.instance = Outcome.objects.get(
outcome=form.data.get(form.add_prefix('outcome')),
outcome_short_name=form.data.get(form.add_prefix('outcome_short_name')))
except Outcome.DoesNotExist:
pass # some other error so we should keep it
else:
del form._errors[NON_FIELD_ERRORS]
Then in your view, when saving the formset, you should loop through all forms and not save the ones where the instance has a pk:
for form in outcomes_formset:
outcome = form.instance
if not outcome.pk:
outcome = form.save()
course.course_outcome.add(outcome)
Here i have two models:
ProfilePic
Member
ProfilePic's user variable extends from Member's username (this is so i can have one username in the DB for all other forms and models).
Now ProfilePic is used as a form, and in my views.py I want to add:
member_obj = Member.objects.get(pk=username)
to my ProfilePic form. However, when I run my code, it doesn't give an error but it doesn't render the information in the db either. So I'm confused as to whats going on here.
What am i doing wrong? Thanks in advance !
# models.py
class ProfilePic(models.Model):
user = models.ForeignKey(Member, related_name='%(class)s_user', null=True)
text = models.TextField(max_length=4096)
thumbnail = models.FileField(upload_to='media', null=True)
class Member(models.Model):
username = models.CharField(max_length=16, primary_key=True)
password = models.CharField(max_length=16)
profile = models.OneToOneField(Profile, null=True)
following = models.ManyToManyField("self", symmetrical=True)
# forms.py
from django import forms
from .models import ProfilePic
class UploadFileForm(forms.ModelForm):
class Meta:
model = ProfilePic
fields = ['text','thumbnail']
# views.py
def profile(request):
username = request.session['username']
member_obj = Member.objects.get(pk=username)
if request.POST:
invitations = Invitation.objects.filter(to_user=username)
form = UploadFileForm(request.POST,request.FILES, instance=member_obj)
form.save()
picture = ProfilePic.objects.all()
return render(request, 'social/profile.html', {
'appname': appname,
'username': username,
'invitations':invitations,
'picture' : picture,
'form' : form,
'loggedin': True}
)
You are passing a Member instance to a ProfilePic model form.
What you want to do is:
form = UploadFileForm(request.POST, request.FILES,
instance=member_obj.profile_pic_user)
So you get a ProfilePic instance.
View is just a function. You get a Member object from the database, assign it to a member_obj variable, but you are not actually doing anything with it. You want to assign it to a ProfilePic object. Also, I don't think this line picture = ProfilePic.objects.all() does what you intend to do. You are getting a list of all profile picture objects instead of just one.
You have to add it to the saved object. You do that by telling the form to create the object, but not saving it to the DB yet.
Then, set the field, and save to the DB.
Add this lines to the view, instead of the form.save():
profile_pic = form.save(commit=False) #not saving to db
member_obj = Member.objects.get(username=request.user.username)
profile_pic.user = member_obj
profile_pic.save() # now it's saved
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.
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.