I need to pass a primary key from a newly created ModelForm to another form field in the same view but I get an error. Any suggestions to make this work?
It looks like in the past, this would be the answer:
def contact_create(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse(contact_details, args=(form.pk,)))
else:
form = ContactForm()
From the documentation, this is what is happening in the newer Django version > 1.8.3
p3 = Place(name='Demon Dogs', address='944 W. Fullerton')
Restaurant.objects.create(place=p3, serves_hot_dogs=True, serves_pizza=False)
Traceback (most recent call last):
...
ValueError: save() prohibited to prevent data loss due to unsaved related object 'place'.
This is how I am getting my pk from the view:
my_id = ""
if form.is_valid():
# deal with form first to get id
model_instance = form.save(commit=False)
model_instance.pub_date= timezone.now()
model_instance.user= current_user.id
model_instance.save()
my_id = model_instance.pk
if hourformset.is_valid():
hourformset.save(commit=False)
for product in hourformset:
if product.is_valid():
product.save(commit=False)
product.company = my_id
product.save()
else:
print(" modelform not saved")
return HttpResponseRedirect('/bizprofile/success')
it is simple:
p3 = Place(name='Demon Dogs', address='944 W. Fullerton')
p3.save() # <--- you need to save the instance first, and then assign
Restaurant.objects.create(
place=p3, serves_hot_dogs=True, serves_pizza=False
)
This was introduced in Django 1.8. Previously you could assign not saved instance to One-To-One relation and in case of fail it was silently skipped. Starting from Django 1.8 you will get error message in this case.
Check a documentation of Django 1.7 -> 1.8 upgrade.
It says:
Assigning unsaved objects to a ForeignKey, GenericForeignKey, and
OneToOneField now raises a ValueError.
If you are interested in more details, you can check save method in django.db.models.base: Some part of it:
for field in self._meta.concrete_fields:
if field.is_relation:
# If the related field isn't cached, then an instance hasn't
# been assigned and there's no need to worry about this check.
try:
getattr(self, field.get_cache_name())
except AttributeError:
continue
obj = getattr(self, field.name, None)
# A pk may have been assigned manually to a model instance not
# saved to the database (or auto-generated in a case like
# UUIDField), but we allow the save to proceed and rely on the
# database to raise an IntegrityError if applicable. If
# constraints aren't supported by the database, there's the
# unavoidable risk of data corruption.
if obj and obj.pk is None:
raise ValueError(
"save() prohibited to prevent data loss due to "
"unsaved related object '%s'." % field.name
)
Last 5 rows are where this error is raised. basically your related obj which is not saved will have obj.pk == None and ValueError will be raised.
Answered - The problem arose from django not saving empty or unchanged forms. This led to null fields on those unsaved forms. Problem was fixed by allowing null fields on foreign keys, as a matter of fact -- all fields. That way, empty or unchanged forms did not return any errors on save.
FYI: Refer to #wolendranh answer.
I just removed my model.save() and the error went away.
This only works if you are saving it when there are no changes.
Otherwise you should save it one time, if you changed something in the queryset.
an example:
views.py
queryset = myModel.objects.get(name="someName", value=4)
queryset.value = 5
# here you need to save it, if you want to keep the changes.
queryset.save()
...
# If you save it again, without any changes, for me I got the error save() prohibited to prevent data loss due to unsaved related object
# don't save it again, unless you have changes.
# queryset.save()
Related
My goal is to perform some additional action when a user changes a value of a existing record.
I found on_model_change() in the docs and wrote the following code:
def on_model_change(self, form, model, is_created):
# get old and new value
old_name = model.name
new_name = form.name
if new_name != old_name:
# if the value is changed perform some other action
rename_files(new_name)
My expectation was that the model parameter would represent the record before the new values from the form was applied. It did not. Instead i found that model always had the same values as form, meaning that the if statement never was fulfilled.
Later i tried this:
class MyView(ModelView):
# excluding the name field from the form
form_excluded_columns = ('name')
form_extra_fields = {
# and adding a set_name field instead
'set_name':StringField('Name')
}
...
def on_model_change(self, form, model, is_created):
# getting the new value from set_name instead of name
new_name = form.set_name
...
Although this solved my goal, it also caused a problem:
The set_name field would not be prefilled with the existing name, forcing the user to type the name even when not intending to change it
I also tried doing db.rollback() at the start of on_model_change() which would undo all changes done by flask-admin, and make model represent the old data. This was rather hacky and lead my to reimplement alot of flask admin code myself, which got messy.
What is the best way to solve this problem?
HOW I SOLVED IT
I used on_form_prefill to prefill the new name field instead of #pjcunningham 's answer.
# fill values from model
def on_form_prefill(self, form, id):
# get track model
track = Tracks.query.filter_by(id=id).first()
# fill new values
form.set_name.data = track.name
Override method update_model in your view. Here is the default behaviour if you are using SqlAlchemy views, I have added some notes to explain the model's state.
def update_model(self, form, model):
"""
Update model from form.
:param form:
Form instance
:param model:
Model instance
"""
try:
# at this point model variable has the unmodified values
form.populate_obj(model)
# at this point model variable has the form values
# your on_model_change is called
self._on_model_change(form, model, False)
# model is now being committed
self.session.commit()
except Exception as ex:
if not self.handle_view_exception(ex):
flash(gettext('Failed to update record. %(error)s', error=str(ex)), 'error')
log.exception('Failed to update record.')
self.session.rollback()
return False
else:
# model is now committed to the database
self.after_model_change(form, model, False)
return True
You'll want something like the following, it's up to you where place the check, I've put it after the model has been committed:
def update_model(self, form, model):
"""
Update model from form.
:param form:
Form instance
:param model:
Model instance
"""
try:
old_name = model.name
new_name = form.name.data
# continue processing the form
form.populate_obj(model)
self._on_model_change(form, model, False)
self.session.commit()
except Exception as ex:
if not self.handle_view_exception(ex):
flash(gettext('Failed to update record. %(error)s', error=str(ex)), 'error')
log.exception('Failed to update record.')
self.session.rollback()
return False
else:
# the model got committed now run our check:
if new_name != old_name:
# if the value is changed perform some other action
rename_files(new_name)
self.after_model_change(form, model, False)
return True
There are similar methods you can override for create_model and delete_model.
I want to create a viewset/apiview with a path like this: list/<slug:entry>/ that once I provide the entry it will check if that entry exists in the database.
*Note: on list/ I have a path to a ViewSet. I wonder if I could change the id with the specific field that I want to check, so I could see if the entry exists or not, but I want to keep the id as it is, so
I tried:
class CheckCouponAPIView(APIView):
def get(self, request, format=None):
try:
Coupon.objects.get(coupon=self.kwargs.get('coupon'))
except Coupon.DoesNotExist:
return Response(data={'message': False})
else:
return Response(data={'message': True})
But I got an error: get() got an unexpected keyword argument 'coupon'.
Here's the path: path('check/<slug:coupon>/', CheckCouponAPIView.as_view()),
Is there any good practice that I could apply in my situation?
What about trying something like this,
class CheckCouponAPIView(viewsets.ModelViewSet):
# other fields
lookup_field = 'slug'
From the official DRF Doc,
lookup_field - The model field that should be used to for performing
object lookup of individual model instances. Defaults to pk
Pretty new to Django. I am trying to switch the ForeignKey field student_information.project back to a null value. As well my student_remove object doesn't seem to be defining properly as 'Remove' should be an object.
Error Code
AttributeError at /project_list/projects/1/
type object 'Student_Information' has no attribute 'student_remove'
Request Method: GET
Request URL: http://127.0.0.1:8000/project_list/projects/1/?Remove=sathya
Django Version: 1.10.5
Exception Type: AttributeError
Exception Value:
type object 'Student_Information' has no attribute 'student_remove'
Exception Location: /media/rms/Sathya's Dr/mysite/projects/views.py in post_detail, line 27
Python Executable: /usr/bin/python
Python Version: 2.7.12
My views.py
def post_detail(request, pk):
post = get_object_or_404(Project, pk=pk)
students = Student_Information.objects.filter(project=post)
if request.GET.get('Remove'):
Remove = request.GET.get('Remove')
obj = Student_Information.objects.get(RCSID=Remove)
#obj.project = None
return render(request, 'projects/post_detail.html', {'post': post, 'students': students})
obj = Student_Information.objects.get(RCSID=Remove)
is throwing an, should specify that RCSID is a foreignkey, it seems like it's trying to find a primary key of 'sathya' where it should just get a string. How do I make it match the string? As if RCSID is automatically RCSID_id.
invalid literal for int() with base 10: 'sathya'
The error message is pretty clear. Student_Information model doesn't have any field named student_remove.
Apart from that, there are so many things wrong with your code.
In Django, you don't update the Model class. You update an instance of Model class. Records are saved as instances of Model class. So line Student_Information.student_remove.project = "---------", needs to be fixed.
student_information.project can be set to null by simply calling student_information.project = None. But here student_information is an instance of Student_Information model.
filter returns a Queryset, not an instance.
You need to call save on the Model instance to update it in the database.
I would recommend you to go through official polls app tutorial.
I made it work by passing id instead of RCSID so that they matched. Easy fix.
I have a Django model with a customer_code field that is set to unique but only some users will have an assigned value for this field. Other users simply use this code to find the user who provided their code as a reference number. When they submit the form however it raises an error as the field is set to unique.
I would like to remove this error upon validation. The user does not get saved with the value it is set to None before save. I have tried doing this so far with a custom clean() method on the form:
def clean(self):
super(EmployeeForm, self).clean()
if 'customer_code' in self.errors:
del self._errors['customer_code']
return self
But this has not been working. All help is appreciated, thanks.
In the end of method you should return cleaned_data
cleaned_data = super(EmployeeForm, self).clean()
...
return cleaned_data
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.