Saving ManytoMany fields in Django with multiple forms - python

I am trying to do the following in 3 forms that are processed at once:
1. Save the subject line for an email (this works)
2. Save the email content. (this works)
3. Save the email title (works) and save the relationship to the subject line and to the email content (not working).
I have read through every page I could which had to do with errors. I have tried it a variety of different ways, but they all haven't worked for me.
View.py:
def email_subject_create_view(request):
if request.method == 'POST':
email_subject_form = EmailSubjectForm(request.POST)
email_content_form = EmailContentForm(request.POST)
email_form = EmailForm(request.POST)
if email_subject_form.is_valid() and email_content_form.is_valid() and email_form.is_valid() :
subject = email_subject_form.save(commit=False)
subject.time_created = datetime.datetime.now()
contractor = Contractor.objects.get(user_id=request.user.id)
subject.save()
email_subject_form.save_m2m()
content = email_content_form.save(commit=False)
content.time_created = datetime.datetime.now()
content.save()
email_content_form.save_m2m()
email = email_form.save(commit=False)
email.time_created = datetime.datetime.now()
# this is what I want to do. email.email_core_contents is a M2M field
email.email_subject_lines = subject.id
email.save()
context = {
'email_subject_form': EmailSubjectForm(),
'email_content_form': EmailContentForm(),
'email_form': EmailForm(),
}
return render(request, 'advertise/email_subject_create.html', context)
I have tried:
email.email_subject_lines = EmailSubject.objects.get(pk=subject.id)
email.email_subject_lines.set(subject)
email.email_subject_lines.set(pk=subject.id)
I have also tried it without the .save_m2m() portions of code.
Edit:
Error in the main example:
Direct assignment to the forward side of a many-to-many set is prohibited. Use email_subject_lines.set() instead.
Error in 1 of the 3 set:
Direct assignment to the forward side of a many-to-many set is prohibited. Use email_subject_lines.set() instead.
Error in the 2 of the 3 set:
"<Email: sfeaesfe>" needs to have a value for field "id" before this many-to-many relationship can be used.
Error in the 3 of the 3 set:
"<Email: graegre>" needs to have a value for field "id" before this many-to-many relationship can be used.

Found two solutions. Here they are in case anyone runs into the same problem in the future.
1: Use the add method.
email.email_subject_lines.add(subject)
email.email_core_contents.add(content)
2. Fetch both objects (Email and EmailSubject) from the database again.
email2 = Email.objects.get(id=email.id)
subject2 = EmailSubject.objects.filter(id=subject.id)
email2.email_subject_lines.set(subject2)
email2.save()
Not sure why it works this second way, but not the original way. Note that I had to use filter instead of get in the second expression.

Related

Using ManyToManyFields() with Django

I'm building a social network where user are supposed to be able to follow each other. So I define a class user with a field: ManyToMany to stock the users that follow this user. This is what I have done in my model.py:
followings = models.ManyToManyField('self', blank=True)
This is my view.py:
#login_required
def follow_test(request):
name = request.POST.get('name', '')
user_followed = Dater.objects.get(username=name)
current_user = Dater.objects.get(id=request.user.id)
print current_user.followings # display my_app.Dater.None
current_user.followings.add(user_followed)
print current_user.followings # display my_app.Dater.None
I retrieve correctly my users (current (The one who follow someone) and the followed one) but I can't add the followed user in the set followings of the current user. Can you see something I don't do properly in my view?
followings is a manager; to show the members of that relationship, you need to call .all() on it (or another manager/queryset method like order_by).
print current_user.followings.all()

using django cleaned data pop to remove data before commit to db

I have a form with a select list.
When the user selects a value of 8888 or 9999 from the award_grant_type select list, I want some of the data that may or may not exist in the form input fields (the user may have entered data into the form text input fields and then selected 8888 or 9999) to be deleted before the form data is commited to the database.
So I have the following model.py code:
.....
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT = 8888
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT = 9999
.....
AWARD_GRANT_TYPES = (
(SELECT_AWARD_AND_GRANT_TYPE, _('Select Type')),
(AWARD, _('Award')),
(GRANT, _('Grant')),
(TRAVEL_GRANT, _('Travel Grant')),
(OTHER_AWARD, _('Other Award')),
(OTHER_GRANT, _('Other Grant')),
(WRITE_MY_OWN_AWARD_AND_GRANT_TYPE_DESCRIPTION, _('Write my own Type description')), #7777
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT, _('Display only Description with prompt')), #8888
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT, _('Display only Description without prompt')) #9999
)
user = models.ForeignKey(User)
language_version = models.ForeignKey('LanguageVersion')
award_grant_type = models.PositiveIntegerField(choices=AWARD_GRANT_TYPES, default=SELECT_AWARD_AND_GRANT_TYPE, validators=[MinValueValidator(1)])
award_grant_type_description = models.CharField(null=True, blank=True, max_length=250)
award_grant_date = models.DateField(null=True, blank=True)
award_grant_description = models.TextField(null=False, blank=False, max_length=5000)
Here is my forms.py clean code that should remove the award_grant_type_description and award_grant_date fields when the user has selected 8888 or 9999 from the select list award_grant_type before being committed to the db:
def clean(self):
cd_agdf = super(AwardGrantDetailsForm, self).clean()
if 'award_grant_type' in cd_agdf:
if cd_agdf['award_grant_type'] == '':
self._errors['award_grant_type'] = self.error_class([_("You must select a Type.")])
elif cd_agdf['award_grant_type'] == 8888 or cd_agdf['award_grant_type'] == 9999:
# remove the entered values when the award grant type only requires minimum data.
self.cleaned_data.pop('award_grant_type_description', None)
self.cleaned_data.pop('award_grant_date', None)
else:
....
return cd_agdf
Can anyone point out what I have done incorrectly? The award_grant_type_description and award_grant_date are not removed before the form data is committed to the db.
EDIT / UPDATE
This issue only occurs when the existing record is updated. A new record removes the data as required before the form is saved to the db. When an existing record has a date field as part of the db record and the award_grant_type is changed from say 1 to 8888 or 9999, then the award_grant_date is NOT removed from the db. I cannot figure out why.
2nd EDIT
I have posted a related thread here.
Try to change self.cleaned_data to cd_agdf. Dictionary, that method clean returns, will be used as cleaned_data. You popped items from self.cleaned_data, but returned not changed cd_ahdf. This is described here (see last step starting with "The form's subclass...").
Answering to the edit only: there is a fight over what the data means.
Form validation happens first, its purpose is to parse the submitted data, clean it up and point any error in the data. The result of this step should be the cleaned up, canonical form of the data.
Then action: if valid, form is acted upon. You interpret that data to take appropriate action. Here, writing it to a database.
Your two steps disagree on the meaning of data. Validation removes award_grant_type_description and award_grant_date from the final data to mean “blank out those fields”. Then, the action interprets missing data as “leave that field as it is” (that's what the default ModelForm.save() does).
Your choice: either conform to ModelForm's convention and set the fields to None instead of removing them, or override your form's save() so it interprets missing data as “remove from object”. Unless I had a good reason to alter ModelForm's semantics, I'd go with setting the fields to None.
I finally figured this issue out.
When I wrap the values in quotation marks, the issue is resolved.
Here is the example code I used that works:
elif cd_agdf['award_grant_type'] == '8888' or cd_agdf['award_grant_type'] == '9999':
I hope that this will help someone.
instead of self.cleaned_data.pop()
do cd_agdf.pop() as you are assigning cd_agdf to superclass

How to get a filed from self.data in model_formset_factory in clean method

I am using modelformset_factory to edit multiple images on my interface.
I have following fields in each image.
Name
User
City
I have allowed user to select new user that is currently not in the system, (for that case I should get a text "Jack" in my
def clean_user(self) instead of ID.
But using model_formseta_factory, I am getting some wired names in my self.data. and when I try to get self.data.get('user'), I get nothing, obviously there is no key with this name,
the key is formed like form_0_user etc.
fields = ['city', 'name']
note, i do not have user in my fields. if I do, it fails the validation.
def clean(self):
data = self.cleaned_data
data['name'] = data.get('name', '').strip()
return data
Works fine
pic_credits = self.data.get('user')
This does not.
pic_credits = self.data.get('form-0-name')
This works fine too.
Please help.
If you want to use self.data instead of self.cleaned_data, you can construct the "composite prefix" using the fields auto_id and prefix (or at least when the form has been instanced by a formset).
See _construct_form() https://docs.djangoproject.com/es/1.9/_modules/django/forms/formsets/
Your method will look like this:
def clean(self):
# ...
form_prefix_and_autoid = "%s-%d-" % (self.prefix, self.auto_id)
pic_credits = self.data.get(form_prefix_and_autoid + 'name')
# ...
Update:
A lot simpler is calling the method self.add_prefix
pic_credits = self.data.get(self.add_prefix('name'))

How to validate django form only when adding not editing

How could we make the django form to not validate if we are editing, not adding a new record. The code as following :
class PageForm(forms.Form):
name = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'textInput'}))
description = forms.CharField(max_length=300, required=False,widget=forms.TextInput(attrs={'class':'textInput'}))
body = forms.CharField(widget=forms.Textarea)
template = forms.CharField(max_length=30,widget=forms.TextInput(attrs={'class':'textInput'}))
navbar = forms.BooleanField(required=False, widget=forms.Select(choices=(('True','True'),
('False', 'False'))))
publish = forms.BooleanField(widget=forms.Select(choices=(('Published','Publish Now'),
('Private','Private'),
('Draft','Draft'))))
def save(self, page=None, commit=True):
data = self.cleaned_data
if not page:
page = models.Page(key_name=data['name'].replace(' ','-'))
page.name = data['name']
page.description = data['description']
page.body = data['body']
page.template = data['template']
page.publish = data['publish']
if commit: page.put()
return page
# prevent the same page 's name
def clean_name(self):
name = self.cleaned_data['name']
query = models.Page.all(keys_only=True)
query.filter('name = ', name)
page = query.get()
if page:
raise forms.ValidationError('Page name "%s" was already used before' % name)
return name
The purpose of this name validation is to prevent the records with the same name. BUt i found that, it also validate on edit, so we couldn't edit records, since it will said 'records with same name already exist'.
Actually for editing, the page param on save function wont be none, but prev record instead, and wil be none on saving a new one. But how we read this param, on clean_name function so we can now whether it is editing or creating?
Thanks a lot!
in your clean method, you can use self.initial to know whether it is adding or editing. If it is editing, the self.initial will not be empty. But when it is adding, self.initial will be dictionary of what the previous value.
If you are editing form, then the form has some instance, and you can check if that exists.
If it does, then you are probably editing existing object.. right?
Example:
If you are editing object with form, you create form object much like this:
form = MyForm(instance = myobject)
Then in your form class methods you can check if form has saved instance in a way that it is described here:
Test if Django ModelForm has instance
in your clean_name function exclude the current object from queryset
query.filter('name = ', name).exclude(pk=self.pk)
or change the if condition to check that page and current object are not the same.
Sorry, I couldn't comment below your guys post, don't know why.
#sunn0 : I didn't use django models, coz deploy the app in appengine, so use appengine model instead.
#Zayatzz : May you show a little code how to do it? Since whether we are adding or editing, we always bound the form to request.POST before validation, so don't know how to differentiate.
#Ashok : I made a workaround based on your suggestion. Since previously I didn't pass the pk to form, but passing the prev object as param instead, so couldn't exclude by using pk. So, I change the code and put additional key as pk (if create, let key empty, but if edit fill key with pk) and just check in if condition, if key field not empty, then it means we are editing. Not sure if it is best practice, but it works anyway.
I can suggest to override form's init method
https://stackoverflow.com/a/70845558/15080117
because there is an argument instance.

How do I edit and delete data in Django?

I am using django 1.0 and I have created my models using the example in the Django book. I am able to perform the basic function of adding data; now I need a way of retrieving that data, loading it into a form (change_form?! or something), EDIT it and save it back to the DB. Secondly how do I DELETE the data that's in the DB? i.e. search, select and then delete!
Please show me an example of the code I need to write on my view.py and urls.py for perform this task.
Say you have a model Employee. To edit an entry with primary key emp_id you do:
emp = Employee.objects.get(pk = emp_id)
emp.name = 'Somename'
emp.save()
to delete it just do:
emp.delete()
so a full view would be:
def update(request, id):
emp = Employee.objects.get(pk = id)
#you can do this for as many fields as you like
#here I asume you had a form with input like <input type="text" name="name"/>
#so it's basically like that for all form fields
emp.name = request.POST.get('name')
emp.save()
return HttpResponse('updated')
def delete(request, id):
emp = Employee.objects.get(pk = id)
emp.delete()
return HttpResponse('deleted')
In urls.py you'd need two entries like this:
(r'^delete/(\d+)/$','myproject.myapp.views.delete'),
(r'^update/(\d+)/$','myproject.myapp.views.update'),
I suggest you take a look at the docs
To do either of these you need to use something called queries.
check link below for really great documentation on that!
(https://docs.djangoproject.com/en/2.2/topics/db/queries/)
To Delete Data:
b = ModelName.objects.get(id = 1)
b.delete()
This will delete the Object of the model w/ an ID of 1
To edit Data:
b = ModelName.objects.get(id = 1)
b.name = 'Henry'
b.save()
This will change the name of the Object of the model w/ an ID of 1 to be Henry
Read the following: The Django admin site. Then revise your question with specific details.

Categories

Resources