I would like to implement my own functionality similar to Django inlineformsets. What I'm interested in is how Django deals with validation and saving of a main object together with it's related objects in inline forms.
Let's say I have two models: Blog and Entry. Entry has a foreign key to the Blog that is not null. I want to be able to create both the blog and it's entries in one place. This is how I would do it using Django inline forms:
blogform = BlogForm(request.POST)
if blogform.is_valid():
tmp = blogform.save(commit=False)
entriesform = EntryInlineFormset(request.POST, instance=tmp)
if entriesform.is_valid():
entriesform.save()
blog.save()
What's going under the hood here? How is Django able to validate entries without blog being saved to the database? I wanted to find this in Django code, but I wasn't able to find the place where they actually do this.
My gues is they create a transaction. They save the blogform and if the entriesform is invalid they rollback. However what if the entriesform is valid, what next? Does the blog instance stay saved? What if save never gets called then.
Or do they span the transaction over two methods (is_valid and save)? I don't think it's best practice to start the transaction in one method and end it in another.
You can validate them both before calling save on either. You can pass a blank instance into both the parent form and the formset.
blog = Blog()
blogform = BlogForm(request.POST, instance=blog)
entriesform = EntryInlineFormset(request.POST, instance=blog)
blog_valid = BlogForm.is_valid()
entries_valid = entriesform.is_valid()
if blog_valid and entries_valid:
... save ...
I validate the forms separately and save the results to variables to avoid short-circuiting.
Related
I have a model with a huge amount of data, and Django creates delete confirmation page a very long time. I have to skip this process and delete data without any confirmation. I have tried some solutions from the internet, but it doesn't work - I still see confirmation page.
Anyone know how to do that?
Django 2.1 released the new ModelAdmin method get_deleted_objects that allows to specify parameters to the confirmation screen for both single and multiple delete (e.g. using the delete action).
In my case, I wanted to delete a list of objects with several relationships, but set with cascade deletion. I ended up with something like this:
def get_deleted_objects(self, objs, request):
deleted_objects = [str(obj) for obj in objs]
model_count = {MyModel._meta.verbose_name_plural: len(deleted_objects)}
perms_needed = []
protected = []
return (deleted_objects, model_count, perms_needed, protected)
I could include other models in the model_count dict, getting only the count, for example, to still avoid list thousands of minor instances that I don't need to see individually.
def delete_selected(modeladmin, request, queryset):
queryset.delete()
class SomeAdmin(admin.ModelAdmin):
actions = (delete_selected,)
I'm working with Django 2.2.10.
I have a model called Site, and a model called Record.
Each record is associated with a single site (Foreign Key).
After my app runs for a few days/weeks/months, each site can have thousands of records associated with it. I use the database efficiently, so this isn't normally a problem.
In Django Admin, when I try to delete a site however, Django Admin tries to figure out every single associated object that will also be deleted, and because my ForeignKey uses on_delete=models.CASCADE, which is what I want, it tries to generate a page that lists thousands, possibly millions of records that will be deleted. Sometimes this succeeds, but takes a few seconds. Sometimes the browser just gives up waiting.
How can I have Django Admin not list every single record it intends to delete? Maybe just say something like "x number of records will be deleted" instead.
Update: Should I be overriding Django admin's delete_confirmation.html? It looks like the culprit might be this line:
<ul>{{ deleted_objects|unordered_list }}</ul>
Or is there an option somewhere that can be enabled to automatically not list every single object to be deleted, perhaps if the object count is over X number of objects?
Update 2: Removing the above line from delete_confirmation.html didn't help. I think it's the view that generates the deleted_objects variable that is taking too long. Not quite sure how to override a Django Admin view
Add this to your admin class, and than you can delete with this action without warning
actions = ["silent_delete"]
def silent_delete(self, request, queryset):
queryset.delete()
If you want to hide default delete action, add this to your admin class
def get_actions(self, request):
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
Since django 2.1 you can override get_deleted_objects to limit the amount of deleted objects listed (it's either a list or a nested list). The timeout is probably due to the django app server timing out on the view's response.
You could limit the size of the returned list:
class YourModelAdmin(django.admin.ModelAdmin):
def get_deleted_objects(self, objs, request):
deleted = super().get_deleted_objects(objs, request)
deleted_objs = deleted[0]
return (self.__limit_nested(deleted_objs),) + deleted[1:]
def __limit_nested(self, objs):
limit = 10
if isinstance(objs, list):
return list(map(self.__limit_nested, objs))
if len(objs) > limit:
return objs[:limit] + ['...']
return objs
But chances are the call to super takes too long as well, so you probably want to return [], {}, set(), [] instead of calling super; though it doesn't tell you about missing permissions or protected relations then (but I saw no alternative other than copy pasting code from django github). You will want to override the delete_confirmation.html and the delete_selected_confirmation.html template as well. You'll also want to make sure the admin has permission to delete any related objects that might get deleted by the cascading deletes.
In fact, the deletion itself may take too long. It's probably best defer the deletion (and permission checks if those are slow too) to a celery task.
I have a model with a huge amount of data, and Django creates delete confirmation page a very long time. I have to skip this process and delete data without any confirmation. I have tried some solutions from the internet, but it doesn't work - I still see confirmation page.
Anyone know how to do that?
Django 2.1 released the new ModelAdmin method get_deleted_objects that allows to specify parameters to the confirmation screen for both single and multiple delete (e.g. using the delete action).
In my case, I wanted to delete a list of objects with several relationships, but set with cascade deletion. I ended up with something like this:
def get_deleted_objects(self, objs, request):
deleted_objects = [str(obj) for obj in objs]
model_count = {MyModel._meta.verbose_name_plural: len(deleted_objects)}
perms_needed = []
protected = []
return (deleted_objects, model_count, perms_needed, protected)
I could include other models in the model_count dict, getting only the count, for example, to still avoid list thousands of minor instances that I don't need to see individually.
def delete_selected(modeladmin, request, queryset):
queryset.delete()
class SomeAdmin(admin.ModelAdmin):
actions = (delete_selected,)
I faintly remember seeing this done before in tutorials in the past. However, im having trouble finding out exactly how in the docs currently.
Suppose we had a model called Post. This model has a field called timestamp. However, when we send this model into the template we don't care about timestamps. Instead, we want the more popular "age" (created X mins/hrs ago), which thankfully, can be deduced from the timestamp.
Instead of creating a whole new field for timestamp, and instead of using custom template tags, can we somehow add a field to a model temporarily before sending it over to our template?
Ex.
# views.py
# Is the below code right? do I need to save()?
posts = Posts.objects.filter(...).filter(...)[:X]
for post in posts:
# Post does not have an age field, we are creating one
# temporarily before sending it to the template
post.age = some_function(post.timestamp)
return render_to_response(template, {'posts' : posts}, etc...)
Thank you.
Just make it a property on the model.
class Post(Model)
#property
def age(self):
return now() - self.timestamp
What is the correct method for validating input for a custom multiwidget in each of these cases:
if I want to implement a custom Field?
if I want to use an existing database field type (say DateField)?
The motivation for this comes from the following two questions:
How do I use django's multi-widget?
Django subclassing multiwidget
I am specifically interested in the fact that I feel I have cheated. I have used value_from_datadict() like so:
def value_from_datadict(self, data, files, name):
datelist = [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
try:
D = date(day=int(datelist[0]), month=int(datelist[1]), year=int(datelist[2]))
return str(D)
except ValueError:
return None
Which looks through the POST dictionary and constructs a value for my widget (see linked questions). However, at the same time I've tacked on some validation; namely if the creation of D as a date object fails, I'm returning None which will fail in the is_valid() check.
My third question therefore is should I be doing this some other way? For this case, I do not want a custom field.
Thanks.
You validate your form fields just like any other fields, implementing the clean_fieldname method in your form. If your validation logic spreads across many form fields (which is nto the same as many widgets!) you put it in your form's clean() method.
http://docs.djangoproject.com/en/1.2/ref/forms/validation/
According to the documentation, validation is the responsibility of the field behind the widget, not the widget itself. Widgets should do nothing but present the input for the user and pass input data back to the field.
So, if you want to validate data that's been submitted, you should write a validator.
This is especially important with MultiWidgets, as you can have more than one aspect of the data error out. Each aspect needs to be returned to the user for consideration, and the built in way to do that is to write validators and place them in the validators attribute of the field.
Contrary to the documentation, you don't have to do this per form. You can, instead, extend one of the built in forms and add an entry to default_validators.
One more note: If you're going to implement a MultiWidget, your form is going to pass some sort of 'compressed' data back to it to render. The docs say:
This method takes a single “compressed” value from the field and returns a list of “decompressed” values. The input value can be assumed valid, but not necessarily non-empty.
-Widgets
Just make sure you're handling that output correctly and you'll be fine.