I need to process applications to an amateur sports event. An event has several distances/subclasses, each of them has some restrictions (age, etc).
My models are
class Event(models.Model):
title = models.CharField(max_length=255)
# more fields
class Klass(models.Model):
title = models.CharField(max_length=255)
capacity = models.IntegerField()
event = models.ForeignKey('Event', related_name="klasses")
# more fields
class TeamRestrictions(models.Model):
age_min = models.IntegerField()
age_max = models.IntegerField()
klass = models.OneToOneField(TeamRestrictions, related_name='restrict')
# more fields
I want to have a single page where a user creates a new event: names it, adds several subclasses into it and restrictions for every subclass. Well, without this one-to-one relationship, for just Event with several Klasses, I could use FormSet.
Of course, I could move all TeamRestrictions fields to Klass, but that looks ugly for me.
What should I use for this more complex structure?
You should create for each model a form and do it separately or you can create really sofisticated form which will do it for you.
This form then would have fields as title (Event), title (Klass), capacity, event, age_min ... so for the relation fields as ForeignKey you will have to use the ChoiceField which will be populated with choices in the __init__ function and so on. Then it should have good cleaning function so that it would have olny valid data and at the end the save. You will have to look if user has selected a field or is creating a new one (such as Event for Klass) and then process them and link and create everything. But that's not the best solution (even it could be in one step) but it is a choice. It could look great even if you added some javascript.
Related
I have a model in my Django project called Job. Each Job has a category. An example of a category could be tutoring. This can be represented as what my model looks like right now:
from __future__ import unicode_literals
from django.db import models
class Job(models.Model):
# Abbreviations for possible categories to be stored in the database.
TUTORING = "TU"
PETSITTING = "PS"
BABYSITTING = "BS"
INTERIOR_DESIGN = "IND"
SHOPPING = "SH"
SOFTWARE_DEVELOPMENT = "SD"
DESIGN = "DE"
ART = "AR"
HOUSEKEEPING = "HK"
OTHER = "OT"
JOB_CATEGORY_CHOICES = (
(TUTORING, 'Tutoring'),
(PETSITTING, "Petsitting"),
(BABYSITTING, "Babysitting"),
(INTERIOR_DESIGN, "Interior Design"),
(SHOPPING, "Shopping"),
(SOFTWARE_DEVELOPMENT, "Software Development"),
(DESIGN), "Design"),
(ART, "Art"),
(HOUSEKEEPING, "Housekeeping"),
(OTHER, "Other"),
)
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
description = models.TextField()
category = models.CharField(max_length=3, choices=JOB_CATEGORY_CHOICES, default=OTHER,)
def __str__(self):
return self.title
Depending on the category of the Job, different fields are required. For example, if I take tutoring as the category again, then extra fields like address, subject, level of study and others are needed. If the category of the Job is software development however, extra fields like project_size and required_qualifications are needed.
Should I create a separate model for each type of Job or is there some kind of model inheritance I can use where job types inherit from the main Job model which holds all the common fields that all Jobs need.
Essentially, what is the best way to have extra fields depending on the Job category?
You have some options:
1. OneToOneField on various category models:
Pro:
allows other models to have FK to Job model. E.g. you could retrieve all of a person jobs via person.jobs.all() no matter which category.
Con:
Allows instances of different categories to relate to the same Job instance: Extra work is needed to maintain data integrity
More tables, more joins, slower queries
Adding a category always entails a migration!
2. Multi-Table inheritance:
Uses OneToOneField under the hood.
Pro:
as above + but each instance of a category will autocreate its own Job instance, so no collisions between categories.
Con:
More tables, more joins, slower queries. Obscures some of the db stuff that's going on.
Adding a category always entails a migration!
3. Job as an abstract base model
Pro: Single table for each category, faster queries
Con: separate relations need to be maintained for each category, no grouping possible at the db level.
Adding a category always entails a migration!
4. Put all the category specific fields in Job (and make them nullable)
Pro: One Table, easy relations, Queries for special categories via filter on category field still possible.
You can use specific model managers to handle categories: Job.tutoring.all()
Possibly many categories share various subsets of fields
No overengineering, easy maintainability.
Adding a new category will only require a migration if it requires a field that is not there yet. You could have a generic CharField used by multiple categories for different semantic purposes and access it via propertys with meaningful names. These cannot, however, be used in filters or qs-updates.
À la:
class Job(models.Model):
# ...
attribute = models.CharField(...)
def _get_attribute(self):
return self.attribute
def _set_attribute(self, value):
self.attribute = value
# for shopping
shop_name = property(_get_attribute, _set_attribute)
# for babysitting
family_name = property(_get_attribute, _set_attribute)
# then you can use
babysitting_job.family_name = 'Miller'
Con: Some fields are null for each job
While options 1-3 may better model the real world and make you feel good about the sophisticated model structure you have cooked up, I would not discard option 4 too quickly.
If the category fields are few and commonly shared between categories, this would be my way to go.
The optimal thing to do would be to use a OneToOneField. Before further explanation, I'll just use this example:
from django.db import models
class Menu(models.Model):
name = models.CharField(max_length=30)
class Item(models.Model):
menu = models.OneToOneField(Menu)
name = models.CharField(max_length=30)
description = models.CharField(max_length=100)
Menu here could compare to your Job model. Once an item in the menu is chosen, the Menu model basically extends the chosen Item's fields. Item here can be compared to your Job category.
You can read more on this stuff here.
I've defined language_tuples = models.ManyToManyField(LanguageTuple) in my UserProfile. This field should be filled when regular user want to became a translator. So he should be able to choose as many as needed tuples of languages - language by language.
EDIT: Thanks to Shang Wang, now I can choose multiple LanguageTuples but I'm not able to create new LanguageTuple objects inside the form.
class Language(models.Model):
shortcut = models.CharField(max_length=40)
name = models.CharField(max_length=40)
def __str__(self):
return self.name
class LanguageTuple(models.Model):
language_from = models.ForeignKey(Language, related_name='language_from', null=True)
language_to = models.ForeignKey(Language, related_name='language_to', null=True)
def __str__(self):
return '{} to {}'.format(self.language_from, self.language_to)
So let's assume that there are multiple Language objects in database already but no instances of LanguageTuple. I want user to be able to built his own tuples (as many as he wants). So if there were languages CZ,EN,GE,SK - he can built for example these tuples: CZ-EN, EN-CZ, GE-CZ, SK-GE etc. - after choosing tuples, those tuples are created inside the database as regular LanguageTuple instances if does not exists.
The problem is that there is no form field inside the form when it is rendered. Don't know what to do with that... as you can see, I've added field - language_tuples into the form.
class TranslatorRegistrationForm(forms.Form):
IBAN = forms.CharField(max_length=40,required=True)
first_name = forms.CharField(max_length=40,required=True)
last_name = forms.CharField(max_length=40,required=True)
class Meta:
model = UserProfile
fields = (
'first_name','last_name','IBAN','language_tuples'
)
One problem I've already mentioned in comment that you need forms.ModelForm for TranslatorRegistrationForm, otherwise django won't recognize all fields you want to display.
If you want user to choose from language_tuples as well as creating new pairs, it's going to be 2 forms. One for your existing form, the other is a form for model LanguageTuple. You need to display both forms in the template, so people could choose either from the list language_tuples or fill out the form for LanguageTuple.
Now be aware that you need some logic in place to detect whether user has chosen an existing language_tuple or trying to use a newly created LanguageTuple. It's some extra steps before you save everything to database but it should be straight forward.
I think I can figure out how to do this in a very ugly way with many, many database queries and lots of horribly cobbled together jQuery, but I'm hoping there's a simpler approach.
I am building a small classification app for a Django website. There are various pieces of media on the site that are classified as belonging to a certain genre, sub-genre, sub-sub-genre, and sub-sub-sub-genre. For instance, a bit of media might be classified as Music (genre), Blues (sub-genre), Memphis Blues (sub-sub-genre), Gospel Blues (a second sub-sub-genre), and 32nd Street Banjo Blues (sub-sub-sub-genre, this one's made up).
Genre, SubGenre, SubSubGenre, and SubSubSubGenre are all models:
class Genre(models.Model):
description = models.CharField(max_length=200, unique=True)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.description
class SubGenre(models.Model):
parent = models.ForeignKey(Genre, related_name='sub_genre')
description = models.CharField(max_length=200, unique=True)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.description
class SubSubGenre(models.Model):
parent = models.ForeignKey(SubGenre, related_name='sub_sub_genre')
description = models.CharField(max_length=200, unique=True)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.description
class SubSubSubGenre(models.Model):
parent = models.ForeignKey(SubSubGenre, related_name='sub_sub_sub_genre')
description = models.CharField(max_length=200, unique=True)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.description
Every piece of media added to the site has a MTM field associating it with one or more of these qualifiers, like so:
class Media(models.Model):
genres = models.ManyToManyField(Genre)
sub_genres = models.ManyToManyField(SubGenre)
sub_sub_genres = models.ManyToManyField(SubSubGenre)
sub_sub_sub_genres = models.ManyToManyField(SubSubSubGenre)
#other fields
Every user on our site has a user profile (an extension of the base user model using a one-to-one relationship). In that user profile, they can tell us what type of media they like to consume.
class UserProfile(models.Model):
user = models.OneToOneField(User)
preferred_genres = models.ManyToManyField(Genre, related_name='genre_pref')
preferred_sub_genres = models.ManyToManyField(SubGenre, related_name = 'subgenre_pref')
preferred_sub_sub_genres = models.ManyToManyField(SubSubGenre, related_name = 'subsubgenre_pref')
preferred_sub_sub_sub_genres = models.ManyToManyField(SubSubSubGenre, related_name = 'subsubsubgenre_pref')
I want to create two forms: one to create a new user profile, and one to create a new piece of Media. The user should be able to define their preferred genres, sub genres, etc. A media uploader should be able to classify their Media in the same way.
We only have a couple of Genres, but each one has a dozen or more sub-genres. Each sub-genre has 20+ sub-sub genres. Most sub-sub genres has 20+ sub-sub-sub genres. I can't just throw all of these options onto the page at once - it's horribly overwhelming.
Here's what I'd like to have happen. Take the "new user profile form" for example. Instead of having the user set their preferred genres, sub-genres, sub-sub genres, and sub-sub-sub genres all at once, I'd like for there to just be a multiple-choice form where the user can set their preferred genre. Then, when the preferred genre is selected (but before the form is submitted), sub-genre choices specific to that genre appear. When a sub-genre is selected, the sub-sub genres who have that sub-genre as a parent then appear as options, etc.
For example, say we have three Genres defined in the system: Music, Book and Podcast. Our new user encounters the question to set their preferred genres and clicks the checkboxes for Music and Book, but leaves Podcast blank. I would like for that user to then be able to set their preferred sub-genres for Music and for Book, and for the CheckboxSelectMultiple fields to be populated only with the sub-genres that have either Music or Book as their parent (Music.subgenre_set.all() or Book.subgenre_set.all() would return the appropriate choices).
For another example, say we have a user who uploads a podcast. On the upload form they encounter the question asking what Genre(s) the Media is in. The user checks the box for "podcast." Now we want to know what sub-genres the Media is in. How do we populate the CheckboxSelectMultiple field with only the sub-genres who have parent "podcast?"
In either example, if "Podcast" is the genre selected, then the appropriate sub-genre choices for the next part of the form would be represented by Podcast.subgenre_set.all(). If one of that podcast's subgenres is "nonfiction," then selecting that subgenre should also bring up the appropriate sub-sub genre choices: Nonfiction.subsubgenre_set.all(). I can't just hardcode the genre, sub-genre, sub-sub genre, and sub-sub-sub genre names however since new ones are certain to be added in the future and that would create a scaling nightmare.
I also feel like just "loading up" every possible whatever_set behind the scenes then unveiling it with javascript trickery puts a heck of a lot of strain on the DB.
If I spread the form out over multiple pages (and multiple queries to the DB), it's easy. But how do I do this all in one single form without annihilating my database?
Thanks for reading all of this!
Firstly, I would reconsider how you are modelling your genres. I think a nicer solution is to have just one model Genre, rather than specifying new models for each SubGenre. This would also allow you to extend to an arbitrary depth without needing to change your model structure.
class Genre(models.Model):
parent = models.ForeignKey(Genre, null=True, related_name='sub_genre')
description = models.CharField(max_length=200, unique=True)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.description
#property
def is_top_level(self):
return bool(self.parent)
Then I would use Form rather than ModelForm with something like this:
class GenreForm(forms.Form):
genre = forms.ModelChoiceField(
queryset=Genre.objects.filter(parent__is_null=True))
sub_genre = forms.ModelChoiceField(
queryset=Genre.objects.filter(
parent__is_null=False, parent__parent__is_null=True))
For the front end, I think the easiest solution is to start with giving each sub_genre options a class='hidden', and use a simple javascript snippet to unhide the options on subsequent fields as parents are selected.
If you have a really large number of genres you might find the most efficient method in terms of page load time is to use ajax to populate the subsequent fields.
Hope it helps!
Firstly, I think you have too many models. All the models have exactly the same fields, so you would be better off just having one model (call it Classification) with name, description, parent, and level: so Genre would have level 0 and no parent, SubGenre would have level 1 and a parent of level 0, and so on. (You could also do this with something like MPTT, but that's probably overkill since ordering is not significant.)
To answer your question though, I think Javascript is the way to go, but not by preloading everything. Instead you should do it with Ajax: on selecting a genre, you do an Ajax request to the server which returns the relevant sub-genres. Since that's such a lightweight request the delay should not be noticeable to the user, so you can then construct the checkboxes or whatever for the user to select.
I have a form in Django called PersonForm this forms model has a one-to-many relationship with Car. When displaying the PersonForm just like in the Django Admin I would like to allow my users to select/deselect from a list of Cars etc. Is this possible? I'm looking for information on where to start.
This is what I have so far for the PersonForm:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('description',)
The Models:
class Person(models.Model):
description = models.CharField(max_length="150")
class Car(models.Model):
make = models.CharField(max_length="25")
owner = models.ForeignKey('Person', related_name="Car")
So in the person form I need to show a list of cars that person is the owner of an allow selecting/deselecting of them. I'm assuming I can do this in the form i.e. using something like the related name.
Sounds like you want an inline model form. This give you the ability to add/remove Car objects from a Person within the Person form.
That previous link was for inlinemodeladmin. This next link is for an inline form:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelforms-factory
I didn't have any chance with inline formset, so i would suggest to override your save method of the model, i feel it's more DRY:
class PersonForm(forms.ModelForm):
# add a field to select a car
car = forms.ModelChoiceField(car.objects.all())
class Meta:
model = Person
fields = ('description', 'car')
def save(self, commit=True):
instance = super().save(commit)
# set Car reverse foreign key from the Person model
instance.car_set.add(self.cleaned_data['car']))
return instance
I know this is an old thread, but since I found myself almost exclusively pointed here by google when searching, I thought I would include the following for anyone else looking for an answer.
The answer, I think, is to use
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelchoicefield
or
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelmultiplechoicefield
There is a good article on how to use the modelmultiplechoicefield at :
https://medium.com/swlh/django-forms-for-many-to-many-fields-d977dec4b024
But it works for one to many fields as well. These allow us to generate a form with multiple choices as checkboxes or similar widgets based upon a related field in a model.
I have Publications and Authors. Since the ordering of Authors matters (the professor doesn't want to be listed after the intern that contributed some trivial data), I defined a custom many-to-many model:
class Authorship(models.Model):
author = models.ForeignKey("Author")
publication = models.ForeignKey("Publication")
ordering = models.IntegerField(default=0)
class Author(models.Model):
name = models.CharField(max_length=100)
class Publication(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, through=Authorship)
I've got aModelForm for publications and use it in a view. Problem is, when I call form.save(), the authors are obviously added with the default ordering of 0. I've written a OrderedModelMultipleChoiceField with a clean method that returns the objects to be saved in the correct order, but I didn't find the hook where the m2m data is actually saved, so that I could add/edit/remove the Authorship instances myself.
Any ideas?
If you are using a custom M2M table using the through parameter, I believe you must do the saves manually in order to save the additional fields. So in your view you would add:
...
publication = form.save()
#assuming that these records are in order! They may not be
order_idx = 0
for author in request.POST.getlist('authors'):
authorship = Authorship(author=author, publication=publication, ordering=order_idx)
authorship.save()
order_idx += 1
You may also be able to place this in your ModelForm's save function.
I'm not sure if there's a hook for this, but you could save it manually with something like:
form = PublicationForm(...)
pub = form.save(commit=False)
pub.save()
form.save_m2m()
So you can handle any custom actions in between as required. See the examples in the docs for the save method.