Creating Django Forms from Querysets - python

I am working on a Django form that let's you order customs pizzas. When building the form to "Select Size" for instance, I want to pull from a database that has my menu item's available sizes in it, so that if the menu changes, the form will automatically know what sizes are available.
Right now I am having an issue with how the query set is returned as it shows up.
How should I alter the code such that
queryset=query_list.values_list('size').distinct()
shows up in the drop down as: Small, Medium
and not: (Small,), (Medium,)
Or is there maybe a way to pull directly from the SIZE_CHOICES tuple in the class?
Thank you!
Models.py:
class Pizza(MenuItem):
STYLE_CHOICES = (
('Regular', 'Regular'),
('Sicilian', 'Sicilian'),
)
SIZE_CHOICES = (
('Small', 'Small'),
('Large', 'Large'),
)
style = models.CharField(max_length=16, blank=False, choices=STYLE_CHOICES)
size = models.CharField(max_length=16, blank=False, choices=SIZE_CHOICES)
num_toppings = models.IntegerField()
toppings = models.ManyToManyField(Topping, blank=True)
is_special = models.BooleanField(default=False)
def toppings_list(self):
return "\n".join([p.name for p in self.toppings.all()])
def __str__(self):
return f"{self.style} {self.size}"
forms.py
class PizzaForm(forms.Form):
query_list = Pizza.objects.all()
style = forms.ModelChoiceField(
widget=forms.Select,
queryset=query_list,
empty_label="Select Size"
)
size_choices = forms.ModelChoiceField(
widget=forms.Select,
queryset=query_list.values_list('size').distinct(),
empty_label="Select Size"
)
topping_choices = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Topping.objects.all(),
required=False
)

This is not a ModelChoiceField [Django-doc], since you do not select a model object, but a value. You thus can use a ChoiceField [Django-doc], and import the choices from the model:
class PizzaForm(forms.Form):
style = forms.ChoiceField(
widget=forms.Select,
choices=Pizza.STYLE_CHOICES,
empty_label="Select Size"
)
size_choices = forms.ChoiceField(
widget=forms.Select,
choices=Pizza.SIZE_CHOICES,
empty_label="Select Size"
)
topping_choices = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Topping.objects.all(),
required=False
)
That being said, you might want to look into a ModelForm [Django-doc]. This can automatically construct a form based on a model, and furthermore remove a lot of boilerplate code to save/update a Pizza object in the database.

Anyone looking to create a choice field for a non-ModelForm where the choices come dynamically from a queryset but are not model instances, you can pass a callable to the choices parameter on a ChoiceField
def _make_choices():
return [(c, c) for c in queryset.values_list('field', flat=True)]
choice_field = forms.ChoiceField(choices=_make_choices)
A Willem said, a ModelForm is the way to go for this problem.

Related

Django - How to render a ModelForm with a Select field, specifying a disabled option?

I have the following models:
# Get or create a 'Not selected' category
def get_placeholder_categoy():
category, _ = ListingCategories.objects.get_or_create(category='Not selected')
return category
# Get default's category ID
def get_placeholder_category_id():
return get_placeholder_categoy().id
class ListingCategories(models.Model):
category = models.CharField(max_length=128, unique=True)
def __str__(self):
return f'{self.category}'
class Listing(models.Model):
title = models.CharField(max_length=256)
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='listings')
description = models.TextField(max_length=5120, blank=True)
img_url = models.URLField(default='https://media.istockphoto.com/vectors/no-image-available-picture-coming-soon-missing-photo-image-vector-id1379257950?b=1&k=20&m=1379257950&s=170667a&w=0&h=RyBlzT5Jt2U87CNkopCku3Use3c_3bsKS3yj6InGx1I=')
category = models.ForeignKey(ListingCategories, on_delete=models.CASCADE, default=get_placeholder_category_id, related_name='listings')
creation_date = models.DateTimeField()
base_price = models.DecimalField(max_digits=10, decimal_places=2, validators=[
MinValueValidator(0.01),
MaxValueValidator(99999999.99)
])
With these, I have the following form:
class ListingForm(ModelForm):
class Meta:
model = Listing
exclude = ['seller', 'creation_date']
widgets = {
'title': TextInput(attrs=base_html_classes),
'description': Textarea(attrs=base_html_classes),
'img_url': URLInput(attrs=base_html_classes),
'category': Select(attrs=base_html_classes),
'base_price': NumberInput(attrs=base_html_classes)
}
One of the available categories I have is "Not selected", since I want to allow that if at some point a category were to be removed, items can be reassigned to that one, however, when rendering the form, I will do some validation on the view function to prevent it from being submitted if the "not selected" category is sent with the form.
Because of this, I want the HTML form on the template to assign the 'disabled' attribute to the option corresponding to that category, however, I have been searching for a couple of days now without finding anything that I was able to understand to the point where I could try it.
Ideally, another thing I'd like to achieve is to be able to modify the order of the rendered options on the form so that I can move to the top 'not selected' regardless of its primary key within the model.
I am aware I can just create a form instead of a model form, or just modify the template so I manually specify how to render the form itself, but I do feel like there is a simple fix to this either on the model or on the model form that I am just not finding yet.
Thanks in advance!
I would suggest you use (in model definition)
class Listing(models.Model):
..
category = model.ForeignKey(ListingCategories, on_delete=models.SET_NULL, null=True, related_name='listings')
..
and optionally in form definition
class ListingForm(ModelForm):
category = forms.ModelChoiceField(ListingCategories, empty_label='Not Selected')
..
While rendering model form, a required attribute will be automatically added, and in form validating, it is also required. It is only in database validation that the field can be left NULL

Slow loading Django Admin change/add

I'm making a classic single view application, mapping multiple
datasources.
Django-admin is paginated so there's no impact when I view my list, the problem is when I want to change/add it is .
Using the debug-toolbar my queries look fine, I don't think they take a long time.
I tried to use a suggestion here Django admin change form load quite slow and created a form, but this had no impact.
When is use exclude = ['e_vehicle','e_product'] it's no surprise that add/change screens load instantly.
Any thoughts please
model.py
class Product_Mapping(Trackable):
product_mapping_id = models.AutoField(primary_key=True)
s_product = models.OneToOneField(sProduct, on_delete=models.CASCADE)
e_fund_manager = models.ForeignKey(eManager, models.DO_NOTHING, blank=True, null=True)
e_product = models.ForeignKey(eProduct, models.DO_NOTHING, blank=True, null=True)
e_vehicle = models.ForeignKey(eVehicle, models.DO_NOTHING, blank=True, null=True)
eManager has around 3K
eProduct has around 17K (has fkey to eManager)
eVehicle has around 25K (has fkey to eProduct)
form.py
class MappingProductForm(forms.ModelForm):
s_product = forms.ChoiceField(required=False,
choices=sProduct.objects.values_list('final_publications_product_id', 'product_name'))
e_fund_manager = forms.ChoiceField(required=False,
choices=eManager.objects.values_list('e_fund_manager_id', 'manager_name'))
e_product = forms.ChoiceField(required=False,
choices=eProduct.objects.values_list('e_product_id', 'product_name'))
e_vehicle = forms.ChoiceField(required=False,
choices=eVehicle.objects.values_list('e_vehicle_id', 'formal_vehicle_name'))
class Meta:
model = Product_Mapping
fields = '__all__'
admin.py
#admin.register(Product_Mapping)
class ChampProductMappingAdmin(admin.ModelAdmin):
form = MappingProductForm
It can be seen that there are too many values in e_product and e_vehicle tables. You are using ChoiceField it means inside HTML dropdown there would be ~17K-25K options this would slow down rendering and sometimes hang on client side.
Solution
edit your forms.py
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
class MappingProductForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MappingProductForm, self).__init__(*args, **kwargs)
rel_model = self.Meta.model
rel_eprod = rel_model._meta.get_field('e_product').rel
rel_eprod = rel_model._meta.get_field('e_vehicle').rel
self.fields['e_product'].widget = ForeignKeyRawIdWidget(rel_eprod, admin_site=admin.site)
self.fields['e_vehicle'].widget = ForeignKeyRawIdWidget(rel_eprod, admin_site=admin.site)
s_product = forms.ChoiceField(required=False,
choices=sProduct.objects.values_list('final_publications_product_id', 'product_name'))
e_fund_manager = forms.ChoiceField(required=False,
choices=eManager.objects.values_list('e_fund_manager_id', 'manager_name'))
e_product = forms.ModelChoiceField(required=False,
choices=eProduct.objects.all())
e_vehicle = forms.ModelChoiceField(required=False,
choices=eVehicle.objects.all()
)
class Meta:
model = Product_Mapping
fields = '__all__'
This would keep raw id as widget. You can get something like below.
You can add other entries by hitting search button besides input.
This is better option when you have lots of choices in ModelChoiceField

django-admin-sortable not saving order of the existing objects

I use django-admin-sortable 2.1.2 and django 1.11.
The problem is that the order is not saving when I try to change it from my admin panel. I think this may be due to already existing model instances.
Here is the part of my current code:
// models.py
class Category(SortableMixin):
name = models.CharField(
_('name'),
max_length=150,
)
order = models.PositiveIntegerField(
default=0,
db_index=True,
)
class Meta:
verbose_name = _('category')
verbose_name_plural = _('categories')
ordering = ['order']
// admin.py
class CategoryAdmin(SortableModelAdmin):
class Meta:
model = Category
fields = (
'name',
)
sortable = 'order'
The default value is set as 0 because of already existing objects. I tried to change their order manually in shell console but it did not help.
I want to avoid deleting my objects and creating them again.
Do you have any ideas how to fix this?
I decided to use another class to inheritance from in my admin.py file.
Instead of:
from suit.admin import SortableModelAdmin
class CategoryAdmin(SortableModelAdmin):
class Meta:
model = Category
fields = (
'name',
)
sortable = 'order'
I use:
from adminsortable.admin import SortableAdmin
class CategoryAdmin(SortableAdmin):
class Meta:
model = Category
fields = (
'name',
)
sortable = 'order'
It works a little different but the effect is satisfying for me and solves my problem.

if form field is None don't find in model in that field

I have model with blank=True fields. In my form I have fields that are optional if in model they are blank=True. So, if in my form I want to make them empty, and then I want to filter my model objects, I don't want to use empty fields to search. Do I need to create other query in this case?
forms.py
class searchGoods(forms.Form):
region_from = forms.ModelChoiceField(required=False, queryset = Region.objects.all(), widget = forms.Select())
region_to = forms.ModelChoiceField(required=False, queryset = Region.objects.all(), widget = forms.Select())
models.py
class Add_good(models.Model):
loading_region = models.ForeignKey(Region, blank=True, related_name="loading_region", null=True)
unloading_region = models.ForeignKey(Region, blank=True, related_name="unloading_region", null=True)
views.py
if form['region_from'] == None:
if form['region_to'] == None:
data_from_db = Add_good.objects.filter(loading_country=form['country_from'],
unloading_country=form['country_to'],
loading_city=form['city_from'],
unloading_city=form['city_to'],
loading_goods_date_from__gte=form['date_from'],
loading_goods_date_to__lte=form['date_to'],
mass__gte=form["mass_from"],
mass__lte=form["mass_to"],
volume__gte=form['volume_from'],
volume__lte=form['volume_to'],
auto_current_type__in=auto_types,
)
else:
data_from_db = Add_good.objects.filter(loading_country=form['country_from'],
unloading_country=form['country_to'],
loading_city=form['city_from'],
unloading_city=form['city_to'],
unloading_region=form["region_to"],
loading_goods_date_from__gte=form['date_from'],
loading_goods_date_to__lte=form['date_to'],
mass__gte=form["mass_from"],
mass__lte=form["mass_to"],
volume__gte=form['volume_from'],
volume__lte=form['volume_to'],
auto_current_type__in=auto_types,
)
else:
if form['region_to'] == None:
data_from_db = Add_good.objects.filter(loading_country=form['country_from'],
unloading_country=form['country_to'],
loading_city=form['city_from'],
unloading_city=form['city_to'],
loading_region=form["region_from"],
loading_goods_date_from__gte=form['date_from'],
loading_goods_date_to__lte=form['date_to'],
mass__gte=form["mass_from"],
mass__lte=form["mass_to"],
volume__gte=form['volume_from'],
volume__lte=form['volume_to'],
auto_current_type__in=auto_types,
)
else:
data_from_db = Add_good.objects.filter(loading_country=form['country_from'],
unloading_country=form['country_to'],
loading_city=form['city_from'],
unloading_city=form['city_to'],
loading_region=form["region_from"],
unloading_region=form["region_to"],
loading_goods_date_from__gte=form['date_from'],
loading_goods_date_to__lte=form['date_to'],
mass__gte=form["mass_from"],
mass__lte=form["mass_to"],
volume__gte=form['volume_from'],
volume__lte=form['volume_to'],
auto_current_type__in=auto_types,
)
Well, exactly, in my models there is more fields, all of them you can see in views when I save them
The situation is that I forget to do makemigrations. After that all works fine!

search_field by field choice

I've a basic django model and i was wondering is there a way to make the search fields search in the value i am mapping to instead of searching in the value saved in the database, is there a possible way that i can search by the value "Premium" ?
Model.py
class User(models.Model):
account = models.ForeignKey('Account')
name =models.CharField(max_length=50)
ACCOUNT_CHOICES = (
(1, 'Premium'),
(0, 'Normal'),)
type = models.CharField(choices=ACCOUNT_CHOICES)
Admin.py
class UserAdmin(admin.ModelAdmin):
search_fields = ['name','type']
pass
admin.site.register(User,UserAdmin)
Summary from comments discussion;
You'll need to set up a custom queryset for the search filter which attempts to reverse lookup the choice display field to its value. Another slight issue is that multiple values can have the same choice display name, so you'll need to take that into consideration.
Here is an example of how to achieve this:
https://github.com/sivaa/django-custom-search-filter/blob/master/app/admin.py
Model:
class order(models.Model):
STATUS = (
("Completed", "Completed"),
("Ordered", "Ordered"),
("Accepted", "Accepted"),
("Order Cancel", "Order Cancel"),
("Customer Cancel", "Customer Cancel"),
("Delivered", "Delivered"),
("Added to Cart", "Added to Cart"),
("Out of Delivery", "Out of Delivery"),
("Refund Initiated","Refund Initiated"),
("Return And exchange","Return And exchange"),
)
status = models.CharField(default="Ordered", max_length=50, null=True, choices=STATUS, blank=True)
View:
status1=request.POST.get("status")
response1=order.objects.filter(status__contains=status1)

Categories