Let's say I have the following model:
class Location(models.Model)
continent = models.CharField(max_length=20)
country = models.ForeignKey(Country)
I need to create a dependent dropdown so that when I select a continent I get all countries belonging to that continent. How should I do it?
Have you read the documentation? It's pretty straightforward. Depends on how you've got your continent/country set up. I'd recommend something like django-cities-light, which provides you with tables populated with countries/regions. I don't think it has continents though.
If you don't want to do that, you need to set up a Country model that has a column for Continent ID for example:
Continent(models.Model):
name = models.CharField()
Country(models.Model):
name = models.CharField()
continent = models.ForeignKey(Continent)
Then in the Location model set the fields thus:
from smart_selects.db_fields import ChainedForeignKey
Location(models.Model):
newcontinent = models.ForeignKey(Continent)
newcountry = ChainedForeignKey(
Country, # the model where you're populating your countries from
chained_field="newcontinent", # the field on your own model that this field links to
chained_model_field="continent", # the field on Country that corresponds to newcontinent
show_all=False, # only shows the countries that correspond to the selected continent in newcontinent
)
From the docs:
This example asumes that the Country Model has a continent = ForeignKey(Continent) field.
The chained field is the field on the same model the field should be chained too.
The chained model field is the field of the chained model that corresponds to the model linked too by the chained field.
Hope that makes sense.
pip install django-smart-selects
Add smart_selects to your INSTALLED_APPS
Bind the smart_selects urls into your project's urls.py. This is
needed for the Chained Selects and Chained ManyToMany Selects. For
example
Your Models
Models
Index.html
Index
Related
I need to create a model form in Django and have the following arbitrary scenario.
Real Estates
============
ID
...some extra fields...
CityID
Cities
======
ID
Name
Region
======
ID
Name
Country
=======
ID
Name
What I would like to do is to let user choose the Country first, then Region and lastly the City. (Populate the child category with javascript after user selects the parent category.) However, I don't want to add the 'Region' and 'Country' fields to the 'Real Estate' table. The order of the fields are also important, that is, 1) Country, 2) Region and 3) City.
Can you suggest any approach to this? Thanks!
I'm assuming you are trying to show the user a limited set of options for "Region" after he selected a country and a limited set of options for "City" after he selected the region so as to provide a way to sensibly select a city for the real estate instead of having to pick something from a long list of random cities?
You could specify additional fields on the RealEstate ModelForm that aren't actual fields on the RealEstate model and provide logic in the form's .__init__() and if needed also in .clean() and .save() methods to take care of the additional fields. Perhaps something like this:
class Country(models.Model):
name = models.CharField(...)
class Region(models.Model):
name = models.CharField(...)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
class City(models.Model):
name = models.CharField(...)
region = models.ForeignKey(Region, on_delete=models.CASCADE)
class RealEstate(models.Model):
name = models.CharField(...)
city = models.ForeignKey(City, on_delete=models.CASCADE)
class RealEstateForm(forms.ModelForm):
country = forms.ChoiceField(required=False)
region = forms.ChoiceField(required=False)
class Meta:
model = RealEstate
fields = ['name', 'city', 'country', 'region']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['country'].choices = Country.objects.all().values('id', 'name)
self.fields['region'].choices = Region.objects.all().values('id', 'name', 'country__id')
self.fields['city'].choices = City.objects.all().values('id', 'name', 'region__id', 'region__country__id')
Providing choices that include the referenced region ID and country ID for each city will allow you to filter these in the front end if you manage to use these choices as the user is selecting country and region without having to make a separate backend request to get the filtered list. Of course, you would end up with a larger response being sent this way in one go - if your data set size is very large (how many countries, how many regions, how many cities) this may not be wanted.
Consider I have the below simple model:
class Dimdate(models.Model):
id = models.AutoField(db_column='Id', primary_key=True) # Field name made lowercase.
date = models.DateField(db_column='date') # Field name made lowercase.
This table is used by many others models (so Dimdate.id is the FK) as below:
class MyModel(models.Model):
id = models.AutoField(db_column='Id', primary_key=True) # Field name made lowercase.
dateid = models.ForeignKey(Dimdate, models.DO_NOTHING, db_column='DateId', blank=True, null=True) # Field name made lowercase.
# ...
My problem is that DimDate table contains too many records. When using Django admin UI to add a new MyModel instance, the dropdown menu is showing all of my DimDate which makes it not user friendly.
I did a quick google search but found nothing to restrict the number of DimDate elements retrieved and displayed in the dropdown menu (when adding a MyModel instance).
Can I filter my dimdate to include only the dates from 1 month in the past to 1 month in the future?
Eg: If we are the 27th of Jan 2020. Dates range is: [27/12/2019, 27/02/2020]
I am currently using the admin "classic" approach of django (no custom form):
#admin.register(models.MyModel)
class MyModelAdmin(admin.ModelAdmin):
I suspect I will need to override the MyModel form. But even by doing it. How can I limit the number of retrieved DimDate inside my form?
Is there an easier way of doing (as I am new with Django...)?
In case it is needed, I am using Django 2.2.6
use raw_id_fields in your admin config to prevent loading all objects
#admin.register(models.MyModel)
class MyModelAdmin(admin.ModelAdmin):
raw_id_fields = ('dateid',)`
it shows up like bellow and you can select object when clicking on it on new window
and if you want just filter dropdown items you can add limit_choices_to to you foreignkey field like bellow:
def limit_dim_date_choices():
return {'date__range': (date(2019,12,27), date(2020,2,27))}
class MyModel(models.Model):
id = models.AutoField(db_column='Id', primary_key=True) # Field name made lowercase.
dateid = models.ForeignKey(Dimdate, limit_choices_to=limit_dim_date_choices, models.DO_NOTHING, db_column='DateId', blank=True, null=True) # Field name made lowercase.
# ...
I am implementing the following schema in a Django app, but I am new to Django's ORM:
Briefly, a DayMenu lists multiple MenuItems. (A MenuItem is simply a many-to-many relationship between a DayMenu and a Meal.) Each User selects a MenuItem from the DayMenu. (This choice is represented as a UserItemChoice.)
In our first draft models.py (below), MenuItem is defined as a many-to-many field on the DayMenu model.
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Meal(models.Model):
# field options: diet
MEAT = "MEAT"
VEGETARIAN = "VEGET"
HALAAL = "HALAA"
DIET_CHOICES = (
(MEAT, "Meat"),
(VEGETARIAN, "Vegetarian"),
(HALAAL, "Halaal"),
)
# field options: type
FREE = "FREE"
PAID = "PAID"
SKIP = "SKIP"
TYPE_CHOICES = (
(FREE, "Free"),
(PAID, "Paid"),
(SKIP, "Skip"),
)
# fields
cost = models.IntegerField(default=10)
description = models.CharField(max_length=120)
diet = models.CharField(max_length=5, choices=DIET_CHOICES)
type = models.CharField(max_length=5, choices=TYPE_CHOICES)
class DayMenu(models.Model):
# fields
date = models.DateField()
locked = models.BooleanField(default=False)
item = models.ManyToManyField(Meal) # TODO: confirm (replaces MenuItem in schema)
# class UserItemChoice(models.Model):
#
# # fields
# user = models.CharField() # FIXME
# menuitem = models.CharField() # FIXME
# selected = models.BooleanField(default=False)
# like = models.NullBooleanField(default=None)
How do we define UserItemChoice given that:
it is itself a many-to-many relationship
it links to a many-to-many field rather than a (explicit) model
it would (ideally?) be a many-to-many field on the built-in user table
I think what you want is to define UserItemChoice as a through model of m2m relationship between User and MenuItem. through model is mainly used when you want to define some extra attributes between the m2m relationship.
Here a user could have multiple MenuItems, but you would also want attributes like selected and like attributes coming with the relationship, but moving these 2 attributes to either model is no good, hence the through is the best solution.
Check out django doc about through definition and example.
One of my models contains a ForeignKey-field to a model that has multiple thousand instances.
When I display a record, all of these are loaded into a dropdown, which I a) don't need and b) is slow as frack, especially when displaying multiple records on one page.
Page size shoots up to multiples of 3.5mb because of the size of the dropdown.
I thought about using "limit_choices_to" to contain that, but
country = models.IntegerField(blank=True, null=True)
location = models.ForeignKey(Geonames, limit_choices_to = {'cowcode': country}, related_name='events')
does not work.
Is there even a way to do that?
Update:
What do I want to display?
I want to show all places (Geonames) that are in the country of the EventRecord that the code above is taken from. I want to show only these places, not the whole list of all possible places.
Why don't I need all places?
a) Page load times: 3.5 minutes for a page load is a tad too long
b) See above: An Event takes place in a certain country, so I don't need to show locations that are not in that country
What you want is to make limit_choices_to aware to your instance, which is not possible.
What you should do is set the queryset property of location field in your admin form, something similar to this:
class EventRecordAdminForm(forms.ModelForm):
class Meta:
model = EventRecord
def __init__(self, *args, **kwargs):
super(EventRecordAdminForm, self).__init__(*args, **kwargs)
self.fields['location'].queryset = Geonames.objects.filter(cowcode=self.instance.country)
and of course use that form for your admin:
class EventRecordAdmin(admin.ModelAdmin):
form = EventRecordAdminForm
See here for docs
HTH!
if you are using admin interface you can use raw_id_fields in ModelAdmin:
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'publisher', 'publication_date')
list_filter = ('publication_date',)
date_hierarchy = 'publication_date'
ordering = ('-publication_date',)
filter_horizontal = ('authors',)
raw_id_fields = ('publisher',)
from Django Book:
sometimes you don’t want to incur the overhead of having to select all the related objects to display in the drop-down. For example, if our book database grows to include thousands of publishers, the “Add book” form could take a while to load, because it would have to load every publisher for display in the box.
The way to fix this is to use an option called raw_id_fields. Set this to a tuple of ForeignKey field names, and those fields will be displayed in the admin with a simple text input box () instead of a select.
Not sure why that is not working for you. But I think a better solution would be to use django-smart-selects. That way you can have the user choose country first. Then the Geoname dropdown is only populated when the user first chooses country.
From the docs:
If you have the following model:
class Location(models.Model)
continent = models.ForeignKey(Continent)
country = models.ForeignKey(Country)
area = models.ForeignKey(Area)
city = models.CharField(max_length=50)
street = models.CharField(max_length=100)
And you want that if you select a continent only the countries are available that are located on this continent and the same for areas you can do the following:
from smart_selects.db_fields import ChainedForeignKey
class Location(models.Model)
continent = models.ForeignKey(Continent)
country = ChainedForeignKey(
Country,
chained_field="continent",
chained_model_field="continent",
show_all=False,
auto_choose=True
)
area = ChainedForeignKey(Area, chained_field="country", chained_model_field="country")
city = models.CharField(max_length=50)
street = models.CharField(max_length=100)
This example asumes that the Country Model has a continent = ForeignKey(Continent) field and that the Area model has country = ForeignKey(Country) field.
In my Django app I allow users to create collections of movies by category. This is represented using 3 models, Movie, Collection, and Addition (the Addition model stores movie, collection, and user instances). Simplified versions of all three models are below.
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
class Addition(models.Model):
user = models.ForeignKey(User)
movie = models.ForeignKey(Movie)
collection = models.ForeignKey(Collection)
So for example a user could create a collection called "80's movies", and add the movie "Indiana Jones" to their collection.
My question is: how do I display a distinct list of movies based on a set of query filters? Right now I am getting a bunch of duplicates for those movies that have been added to more than one collection. I would normally use distinct() to get distinct objects, but in this case I need distinct movies rather than distinct additions, but I need to query the Addition model because I want to allow the user to view movies added by their friends.
Am I setting up my models in an optimal way? Any advice/help would be much appreciated.
Thanks!
First. I don't think you need Addition model here. You try to create many-to-many relation, but there's documented way of doing this:
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
movies = models.ManyToManyField('Movie', blank=True, null=True)
Second. The documentation says: "To refer to a "reverse" relationship, just use the lowercase name of the model".
So the answer is (for the setup above):
Movie.objects.filter(collection__user=user).distinct()