showing a field of multiplke choises in django admin - python

In my django admin I'm trying to show a field of my model which may has multiple values (like a list). Here's my definition of the field in models.py
related_countries = CountryField(
multiple=True,
blank=True
)
So when I create a model in the database, what I got as the value of the field is something like
AL,AS
Then for my admin page, I didn't put it in the list_display because I don't want it to be shown at the page where all the records of this models is printed. I want it to be shown only when I click one of the record and check the detail of this record. So when I'm at the page that shows everythhing, it works well.
Also, the record cannont be modified on the admin page so I have this function on my admin code:
def has_change_permission(self, request: HttpRequest, obj=None) -> bool:
return False
And there comes the issue: When I enter the page for the detail of the record, I got a TypeError at XXX unhashable type: 'list' and I'm pretty sure it comes from the field related_countries. Cause when I removed the function to make it possible to modify the record, anything works well cause there's a bloc for the related_countries to let me modify the countries. But when it becomes impossible to modify anything, it seems like the admin doesn't know how to present the list of the countries.
I tried to add this function but it didn't work:
def get_related_countries(self, obj):
return ",".join([c for c in obj.related_countries])
It doesn't work. I even tried to make it return an empty list but still not working. I guess the function is never called.
I'm not sure if I should add codes for serializers.py or views.py. It's empty for now.

Related

How do I prevent the Django admin delete confirmation view to display all the objects to be deleted? [duplicate]

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,)

How can I deal with a massive delete from Django Admin?

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.

Flask + WTForms, dynamically generated list of fields

I am making a Flask application that is essentially form-based and so I'm using WTForms and Flask-wtf.
I am currently refactoring my code so my whole form uses WTForms and there is a very dynamic part of one of the forms that I am unable to implement using WTForms. I have no clue how to do it, my initial ideas didn't work, I can't find references or tutorials covering my problem and so this is why I ask for help.
So, the form in question allows users to submit objects that consist of:
A label (StringField, easy)
A Description (TextAreaField, also easy; although I had trouble to make a default value work)
A list of property of the form (predicate, object), where predicate is taken from a pre-built list and object can basically be anything but each predicate will generate a specific object (for instance, the predicate "related to" will expect another object (that comes from a dropdown list) and the predicate "resource" will expect a http link of some sort). This list can be empty.
As you can guess I have trouble with the list. The way the code works right now, I get the label and description using wtforms, and the property list is generated using a config constant (that is used throughout the code so I only have one place to edit if I want to add new properties) and a dynamic menu in javascript that creates (here, for predicates) fields, that I can then get using flask.request.form object in the view function. All the hidden fields for predicates have the same name attribute and all the hidden fields for objects have the same name attribute.
Here is what the view of the form looks like, initialized with a few properties:
http://i.imgur.com/bfMG95s.png
Under the "Propriétés" label you have a dropdown to select the predicate, the second field is displayed or hidden depending on the selected predicate (can be a dropdown or a text field), and it is only when you click on "Ajouter propriété" ("Add property") that a new line is added in the tab below and the fields are generated.
I'd like not to have to change anything on this side because it works very well, makes the form very intuitive and is basically exactly what I want it to be from the user's end.
This is what my custom Form looks like right now (it doesn't work and properties stays empty whatever the number of fields I submit with the form):
class PropertyForm(Form):
property_predicate = HiddenField(
validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())]
)
property_object = HiddenField(
validators=[DataRequired()]
)
class CategoryForm(Form):
"""
Custom form class for creating a category with the absolute minimal
attributes (label and description)
"""
label = StringField(
"Nom de la categorie (obligatoire)",
validators=[DataRequired()]
)
description = TextAreaField(
"Description de la categorie (obligatoire)",
validators=[DataRequired()]
)
properties = FieldList(FormField(PropertyForm),validators=[Optional()])
And here is what I'd love to do in my views.py code (that I am currently refactoring):
def cat_editor():
cat_form = CategoryForm()
if request.method == "GET":
# Do GET stuff and display the form
return render_template("cateditor.html", form=cat_form, varlist=template_var_list)
else if request.method == "POST":
if cat_form.validate_on_submit():
# Get values from form
category_label = cat_form.label.data
category_description = cat_form.description.data
category_properties = cat_form.properties.data
# Do POST stuff and compute things
return redirect(url_for("index"))
else:
# form didn't validate so we return the form so the template can display the errors
return render_template("cateditor.html", form=cat_form,
template_var_list = template_var_list)
The basic structure works perfectly, it's just that damn dynamic list I can't get to work properly.
Getting label and description from the WTForms CategoryForm instance works fine, but properties always return an empty list. Ideally I'd love to be able to get a list of the form [(predicate1, property1), (predicate2, object2) ... ] when calling cat_form.properties.data (this is why I have a FieldList of FormFields with two HiddenField in each) but I'd have no problem having to build such a list from two list as long as it's using WTForms. Any idea? Thanks a lot :)
I found out what the problem was by playing around with FieldList objects and append_entry() to see what HTML code would Flask-wtf generate if I was to make a prepopulated property list.
My Javascript was generating hidden fields with all the same name, as from what I understood that WTForms is able to aggregate fields with the same name to create lists. Problem is, those similarly named fields were part of a FormField itself nested in a FieldList object name properties.
In order for the WTForms Form object to discern a set of hidden fields from another, when you nest FormFields inside a FieldList it prefixes the FormFields field names with "FieldList_name-index-". Which means what WTForms was expecting was something like
<input type="hidden", name="properties-0-property_predicate" value=...>
<input type="hidden", name="properties-0-property_object" value=...>
<input type="hidden", name="properties-1-property_predicate" value=...>
<input type="hidden", name="properties-1-property_object" value=...>
<input type="hidden", name="properties-2-property_predicate" value=...>
<input type="hidden", name="properties-2-property_object" value=...>
I modified my javascript so it generates the appropriate names. Now when I call cat_form.properties.data I have something that looks like:
[{"property_predicate": "comment", "property_object":"bleh"},
{"property_predicate": "comment", "property_object": "bleh2"}]
And that is exactly what I need. For some reason the form doesn't validate but at least I know how to make WTForms extract data my javascript-generated hidden fields, which is what the problem was.
Edit: Form validation happens because you have to insert a CSRF hidden input with your csrf to every subform you generate with the FormField.
Use jQuery for the more dynamic elements/ behavior in your form(s). Note that form fields have a hidden property (or method, depending e.g., if you're using bootstrap), allowing you to render everything you might need, but only show fields when these are necessary, and hiding them otherwise. Dynamically adding fields is a bit harder, but not really impossible. Is there a limit to the number of fields associated with properties? if yes, i'd just render the maximum number of fields (as long as it's reasonable, up to 5 seems OK, when you get to double digits as a maximum number of properties a user can add, rendering a bunch of fields you'll never use gets to be inelegant).
Here's a good place to see how that would work. Of course, you have another problem of choosing when to hide or show relevant fields, but that can also be handled by a javascript/jQuery script, using jQuery's .change() event. Something like this:
$("#dropdown").change(function () {
var chosen_val = $(this).val();
if (chosen_val == 'banana'){$('#property1').show();} else {$('#property1').hide();}
});
This code will probably not work, and is definitely lacking proper logic but should give you an idea of how to approach this issue using jQuery. Note that 'property1' field is always there, waiting to be shown if the user chooses the right dropdown value.

How to update form with errors in Django?

I'm trying to write an is_valid method for my forms and I'm using this general strategy:
def is_valid(self):
form = super(UserCreateForm, self).is_valid()
for f, error in self.errors.iteritems():
if f!= '__all__':
self.fields[f].widget.attrs.update({'class': 'error', 'value': strip_tags(error)})
return form
I want to update the form fields' attributes if I get an error with helpful attributes. But I already have class attributes for the fields (using Bootstrap, so something like 'class':'form-control'); I want the error to replace them. However, when I fail the validation and actually get an error, Django complains saying can't concatenate str and errorList. I'm a bit new to Django, so I'm not sure what is going on here.
The way i do this is, have the the errors checked at all the individual divs and display bootstrap's .error class if i find a error.
Eg:{% if form.error %}
Like that you check it with every filed name.
Note: There might be better ways to do it, but i have been using this for a long time now and has worked fine.

How to properly validate a MultipleChoiceField in a Django form

I have a MultipleChoiceField representing US states, and passing a GET request to my form like ?state=AL%2CAK results in the error:
Select a valid choice. AL,AK is not one of the available choices.
However, these values are definitely listed in the fields choices, as they're rendered in the form field correctly.
I've tried specifying a custom clean_state() method in my form, to convert the value to a list, but that has no effect. Printing the cleaned_data['state'] seems to show it's not even being called with the data from request.GET.
What's causing this error?
from django import forms
class MyForm(forms.Form):
state = forms.MultipleChoiceField(
required=False,
choices=[('AL','Alabama'),('AK','Alaska')],
)
MultipleChoiceFields don't pass all of the selected values in a list, they pass several different values for the same key instead.
In other words, if you select 'AL' and 'AK' your querystring should be ?state=AL&state=AK instead of ?state=AL%2CAK.
Without seeing your custom clean_state() method I can't tell you what's going wrong with it, but if the state field isn't valid because the querystring is wrong then 'state' won't be in cleaned_data (because cleaned_data only holds valid data).
Hopefully that helps. If you're still stuck try adding a few more details and I can try to be more specific.

Categories