I'm trying to create a multi-select list of checkboxes for names in a database.
I thought I could do the below in the from, and do some kind of loop in the template to render each name, but noting I try works. Is this the correct approach, any clues on how to render this in the template?
Thanks
from django import forms
from .models import Player
class PlayerForm(forms.Form):
team = forms.MultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
choices=[Player.objects.all()]
)
from django.db import models
class Player(models.Model):
lname = models.CharField(max_length=10, verbose_name='Last Name')
fname = models.CharField(max_length=10, verbose_name='First Name')
wins = models.SmallIntegerField(default=0, null=True, blank=True)
loss = models.SmallIntegerField(default=0, null=True, blank=True)
def __str__(self):
return "{}".format(self.lname)
class Meta:
ordering = ['lname']
Not entirely. If you need to select a option among model options, you should use a ModelMultipleChoiceField field [Django-doc]. This will not only make it more convenient to work with data, but it will furthermore each time query the database, such that, if you add a new Player, one can select that one.
You thus can implement this as:
class TeamForm(forms.Form):
team = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Player.objects.all()
)
It might furthermore be better to name your form TeamForm, since you here do not create/update/... a Player, but you select a team.
Related
I have a Slider module that i want to include items from movies_movie and shows_show table. An item can either be a show or movie. How do i make user select between movie and show? Currently i have columns for movie and show but how do i force user to select between the two?
also title_en is a column in movie or tv show tables. So the title of the movie/show selected should display in row after save.
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True)
def __str__(self):
return self.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
Also if a show is selected and a movie isn't, how do i know title_en will be taken from show and not movie?
I think you can do something like this:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
# don't forget to add null=True to both fields
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True, null=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True, null=True)
# see docs, https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.clean
def clean(self):
if self.movie and self.show:
raise ValidationError({'movie': _('You can't select both types at the same time')})
elif not self.movie and not self.show:
raise ValidationError({'movie': _('You must select one type')})
def __str__(self):
return self.movie.title_en if self.movie else self.show.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
You may consider using django contenttypes.
Imagine in the future, you have not just Movie, Show, but have new Class such as Book, Podcase, it might not be a good idea to keep adding new foreignkey to your Slider Model.
I have not used contenttype before, so I am referencing this SO answer.
(using python 3.6, django 3.2)
models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Movie(models.Model):
title = models.CharField(max_length=50)
director = models.CharField(max_length=50)
class Show(models.Model):
title = models.CharField(max_length=50)
date = models.DateField()
class Slider(models.Model):
order = models.IntegerField(max_length=3, blank=True)
choices = models.Q(model='movie') | models.Q(model='show')
selection_type = models.ForeignKey(
ContentType, limit_choices_to=choices,
on_delete=models.CASCADE)
selection_id = models.PositiveIntegerField()
selection = GenericForeignKey('selection_type', 'selection_id')
def __str__(self):
return self.selection.title
admin.py
#admin.register(Slider)
class SliderAdmin(admin.ModelAdmin):
pass
at django shell, the following is valid.
movie = Movie.objects.create(title='movie 1', director='ben')
show = Show.objects.create(title='show 1', date='2021-01-01')
s1 = Slider.objects.create(selection=movie, order=1)
s2 = Slider.objects.create(selection=show, order=2)
However, using limit_choices_to only restrict the choices in admin page, and there is no constraint at database level. i.e. the following are actually legal.
place = Place.objects.create(name='home')
s3 = Slider.objects.create(selection=s3, order=3)
I have not found a fix for this issue yet. Maybe doing some validation in save method is a way (see the comments under this).
core/models.py
from django.db import models
from django.db.models.signals import post_save
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
middle_name = models.CharField(max_length=30)
class Company(models.Model):
name = models.CharField(max_length=100)
class Entity(models.Model):
is_person = models.BooleanField(default=True)
person = models.ForeignKey(Person, on_delete=models.PROTECT, null=True)
company = models.ForeignKey(Company, on_delete=models.PROTECT, null=True)
name = models.CharField(max_length=30)
def __str__(self):
return self.name
#property
def title(self):
return self.name
class Meta:
verbose_name_plural = 'entities'
def post_save_person_receiver(sender, instance, created, *args, **kwargs):
if created:
entity, is_created = Entity.objects.get_or_create(is_person=True, person=instance, company=None, name=instance.last_name) # noqa
post_save.connect(post_save_person_receiver, sender=Person)
def post_save_company_receiver(sender, instance, created, *args, **kwargs):
if created:
entity, is_created = Entity.objects.get_or_create(is_person=False, person=None, company=instance, name=instance.short_name) # noqa
post_save.connect(post_save_company_receiver, sender=Company)
class Group(models.Model):
name = models.CharField(max_length=20)
is_individual = models.BooleanField(default=True)
members = models.ManyToManyField(Entity, through='Membership')
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.PROTECT, null=False)
entity = models.ForeignKey(Entity, on_delete=models.PROTECT, null=False)
class Meta:
unique_together = ("entity", "group")
For every Company and Person created, an Entity is automatically created where Entity.is_person=True if it's a Person. An Entity can then become a member of a Group such as 'Employee', 'Supplier' and 'Customer' through a ManyToMany relationship in the Membership Model.
How do I filter Membership.entity in Admin View (for add and update) that when the Group selected is an 'is_individual=True', such as 'Employee', Entity Field only shows 'is_person=True' Persons in the Entity combobox?
Admin View
I would consider writing your own view for this. In my opinion, Djangos admin is not a good site to base a frontend on. I use it only for making quick changes where validation doesn't need to be done as much because the users of admin should know what they're doing. When you start adding more responsive stuff, that's when I consider using another view and starting from scratch (or a framework) but a different view none-the-less.
There are several different names for what it sounds like you want: dependent dropdowns, chained fields, etc. Django itself doesn't have anything out of the box for this.
Your 2 options are: (1) do it yourself or (2) use a 3rd-party package.
As far as doing it yourself goes, you're going to need to do some work in JS for making it work on the frontend, and you're probably going to need some kind of flexible view that outputs JSON data, and you're going to need some kind of custom select field to handle this.
For using a 3rd-party package, I don't see anything particularly recent that does this. Here's one that's a few years old: https://github.com/runekaagaard/django-admin-flexselect
Hope this helps some!
(Django 1.8.14)
So I have one model, which uses a ContentType to point to a collections of models across separate apps.
I want to be able to "plug in" the functionality linking that model to the other models in the separate app. So we have the follwing:
class MyModelMixin(models.Model):
""" Mixin to get the TheModel data into a MyModel
"""
updated_date = models.DateTimeField(null=True, blank=True)
submission = GenericRelation(
TheModel,
related_query_name='%(app_label)s_the_related_name')
class Meta:
abstract = True
The main model model looks like this:
class TheModel(models.Model):
""" This tracks a specific submission.
"""
updated = models.DateTimeField()
status = models.CharField(max_length=32, blank=True, null=True)
final_score = models.DecimalField(
decimal_places=2, max_digits=30, default=-1,
)
config = models.ForeignKey(Config, blank=True, null=True)
content_type = models.ForeignKey(
ContentType,
blank=True,
null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
my_model_instance = GenericForeignKey()
And the mixin is included in the MyModel models, which are available in many different apps.
When using a filter for this, using django filters, there is an issue. I have a filter that's supposed to be instantiated to each app when it's used. However when I instantiate, for example, with
class MyFilterSet(Filterset):
def __init__(self, *args, **kwargs):
self.config_pk = kwargs.pop('config_pk', None)
if not self.config_pk:
return
self.config = models.Config.objects.get(pk=self.config_pk)
self.custom_ordering["c_name"] =\
"field_one__{}_the_related_name__name".format(
self.config.app_model.app_label,
)
super(MyFilterSet,self).__init__(*args, **kwargs)
However, when I use this, I get
FieldError: Cannot resolve keyword 'my_app_the_related_name field. Choices are: %(app_label)s_the_related_name, answers, config, config_id, content_type, content_type_id, final_score, form, form_id, id, object_id, section_scores, status, updated
How can %(app_label)s_the_related_name be in the set of fields, and how can I either make it render properly (like it does outside of django filters) or is there some other solution.
You have probably encountered issue #25354. Templating related_query_name on GenericRelation doesn't work properly on Django 1.9 or below.
Added in Django 1.10 was related_query_name now supports app label and class interpolation using the '%(app_label)s' and '%(class)s' strings, after the fix was merged.
I am defining some models for an inventory / work order application I am developing and I've run into a slight sticking point.
Here are some of the models that I'm currently having some difficulty with.
#models.py
from django.db import models
class Staff(models.Model):
ROLE = (
('M', 'Mechanic'),
('W', 'Warehouse'),
)
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=25)
staff_role = models.CharField(max_length=1, choices=ROLE)
class WorkOrder(models.Model):
item_number = models.ForeignKey(Item)
date_started = models.DateField()
date_ended = models.DateField()
mechanic = models.ForeignKey(Staff)
What I would like is for a work order to only be associated with a staff member whose role is set as "Mechanic". Is there a way to restrict this within the model specification based on the models that I have here, or should I deal with this downstream when I set up the views and the forms?
Use the limit_choices_to parameter to ForeignKey:
mechanic = models.ForeignKey(Staff, limit_choices_to={'staff_role': 'M'})
I have the model( this just example in real life model is much bigger):
class Ride(models.Model):
account = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='dives')
date = models.DateField(blank=True, null=True)
referenceA = models.ForeignKey(
RefA,
related_name="rides",
blank=True,
null=True
)
# in real life there is much more options and group of option
optionA = models.FloatField(
blank=True, null=True
)
optionB = models.FloatField(
blank=True, null=True
)
I have divided this model like this:
class Ride(models.Model):
account = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='dives')
date = models.DateField(blank=True, null=True)
referenceA = models.ForeignKey(
RefA,
related_name="rides",
blank=True,
null=True
)
ride_options = models.OneToOneField(
RideOption
)
class RideOption(models.Models):
optionA = models.FloatField(
blank=True, null=True
)
optionB = models.FloatField(
blank=True, null=True
)
Now I want to create a page which edit Ride model instance with all related model instances(RideOption, ...).
I prefer to use ModelForm for each model but how can I validate it all together.
I can write this validation in view, something like this:
ride_form = RideModelForm(...)
ride_option_form = RideOptionModelForm(...)
if ride_option_form.is_valid():
if ride_form.is_valid():
# now save
but as for me it's really ugly and I can have a lot of related model.
Is there a way to hide this validation and saving internal?
I looked on FormSet but as I understand they works only for Models with Foreign relation.
Maybe somebody knows how to solve this with formset?
Or another(not ugly) way to do that?
It sounds like you want an Inline Formset
"Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key."
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets
A OneToOneField is just a special case of ForeignKey and ought to work the same.
eg
# forms.py
from django.forms.models import inlineformset_factory
from .models import Ride, RideOption
RideFormSet = inlineformset_factory(Ride, RideOption, extra=0)
# views.py
from django.shortcuts import get_object_or_404
from .forms import RideFormSet
from .models import Ride
def myview(request, ride_id):
ride = get_object_or_404(Ride, pk=ride_id)
formset = RideFormSet(data=request.POST or None, instance=ride)
if formset.is_valid():
formset.save()
# maybe redirect to success url here
# else render template here