django: smart-select ChainedForeignKey / chained dropdown in admin - django

Hej! :)
I have 5 models which are connected hierarchical with each other.
Section -> division -> group -> class -> wz
one section can have multiple divisions, but one division can only have one section (and so on). Therefor I have ForeignKeys set:
# models.py
class NaceSection(models.Model):
code = models.CharField(max_length=1, unique=True)
description_english = models.CharField(max_length=500)
class NaceDivision(models.Model):
code = models.CharField(max_length=2, unique=True)
nace_section = models.ForeignKey(NaceSection, on_delete=models.CASCADE, related_name="nace_section")
description_english = models.CharField(max_length=500)
class NaceGroup(models.Model):
nace_division = models.ForeignKey(NaceDivision, on_delete=models.CASCADE, related_name="nace_division")
code = models.CharField(max_length=4, unique=True)
description_english = models.CharField(max_length=500)
I than have a model where all those are integrated as M2M fields with a dropdown option.
My goal is to only get the divisions which are in the already selected section in the admin area. (and so on)
I tried smart-select ChainedForeignKey:
# models.py
class Institution(models.Model):
nace_sections = models.ManyToManyField(
NaceSection,
related_name="nace_sections"
)
nace_divisions = ChainedForeignKey(
NaceDivision,
chained_field="nace_sections",
chained_model_field='nace_sections',
blank=True,
)
nace_group = ChainedForeignKey(
NaceGroup,
chained_field="nace_divisions",
chained_model_field='nace_divisions',
blank=True,
)
The organisation and dropdown in the admin area do not change at all and my view with a table of all my results tells me ('42S22', "[42S22] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid column name 'nace_divisions_id'. (207) (SQLExecDirectW)")
With the ChainedManyToManyField nothing at all happens. Does anybody know what's going wrong?
Any help appreciated! :)

nace_sections is specified as models.ManyToManyField in your code
1.Change from ManyToManyField to ForeignKey.
2.Rename chained_model_field values
-->nace_sections to nace_section
-->nace_divisions to nace_division
class Institution(models.Model):
nace_sections = models.ForeignKey(
NaceSection,
related_name="nace_sections"
, on_delete=models.CASCADE
)
nace_divisions = ChainedForeignKey(
NaceDivision,
chained_field="nace_sections",
chained_model_field='nace_section',
blank=True,
)
nace_group = ChainedForeignKey(
NaceGroup,
chained_field="nace_divisions",
chained_model_field='nace_division',
blank=True,
)

Related

Should i create one model for multiple apps in a clothing project?

It's my first time creating a project with Django. and it's an e-commerce store for clothing. my confusion now is, most of the items are similar, like women's wears, men's wears, children's wears.Creating different models in different apps means i have to repeat a lot of code because most of the fields will be same and some operations would be more complicated to achieve like global search, etc.
So should i create a single database for all of the items and filter out what i need in different sections like menswear page, women's wear page, etc ?
#For men's model
from django.db import models
from django.urls import reverse
# Create your models here.
CLOTH_CATEGORY_OPTIONS = (
("top", "Top"),
("bottom", "Bottom"),
("complete", "Complete"),
)
CLOTH_GENDER_OPTIONS = (
("male", "Male"),
("female", "Female"),
("unisex", "Unisex"),
)
class Wear(models.Model):
cloth_name = models.CharField(max_length=50, unique=True)
cloth_image = models.CharField(max_length=300)
cloth_category = models.CharField(
max_length=8, choices=CLOTH_CATEGORY_OPTIONS, default='top')
cloth_gender = models.CharField(
max_length=8, choices=CLOTH_GENDER_OPTIONS, default='male')
cloth_price = models.FloatField()
cloth_description = models.TextField()
slug = models.SlugField(default='', editable=False,
max_length=200, null=False)
#For women's model
from django.urls import reverse
# Create your models here.
CLOTH_CATEGORY_OPTIONS = (
("top", "Top"),
("bottom", "Bottom"),
("complete", "Complete"),
)
CLOTH_GENDER_OPTIONS = (
("male", "Male"),
("female", "Female"),
("unisex", "Unisex"),
)
class WomensWear(models.Model):
cloth_name = models.CharField(max_length=50, unique=True)
cloth_image = models.CharField(max_length=300)
cloth_category = models.CharField(
max_length=8, choices=CLOTH_CATEGORY_OPTIONS, default='top')
cloth_gender = models.CharField(
max_length=8, choices=CLOTH_GENDER_OPTIONS, default='male')
cloth_price = models.FloatField()
cloth_description = models.TextField()
slug = models.SlugField(default='', editable=False,
max_length=200, null=False)
As you can see, this is repetitive, so i want to create a global database so i can import it in each app and filter out for men, women, children etc.
You didn't need to repeat all this code for each gender..
Make one model with a field Gender and specify all the genders.
You can filter the clothes in the views with:
Wear.objects.all().filter(Gender=**Male or Women**)
That will filter your products with the gender you give.
Creating 2 models will work for you. A Cloth Model and a Gender Model
One cloth can be of a male or female or both so will work with one to many
class Cloth(models.Model):
gender = models.ForiegnKey(Gender)
....
You will be filtering using:
Cloth.objects.filter(gender__type="male")

Optimizing Django with prefetch and filters in large table

I had a database in php/html using MySQL and am transferring this to a Django project.
I have all the functionalities working, but loading a table of the data I want is immensely slow because of the relations with other tables.
After searching for days I know that I probably have to use a model.Manager to use prefetch_all. However, I am not stuck on how to call this into my template.
I have the following models(simplified):
class OrganisationManager(models.Manager):
def get_queryset_director(self):
person_query = Position.objects.select_related('person').filter(position_current=True,
position_type="director"
)
return super().get_queryset().prefetch_related(Prefetch('position_set', queryset=person_query, to_attr="position_list"))
def get_queryset_president(self):
person_query = Position.objects.select_related('person').filter(position_current=True,
position_type="president"
)
return super().get_queryset().prefetch_related(Prefetch('position_set', queryset=person_query, to_attr="position_list"))
class Person(models.Model):
full_name = models.CharField(max_length=255, blank=True, null=True)
country = models.ForeignKey(Country, models.CASCADE, blank=True, null=True)
birth_date = models.DateField(blank=True, null=True)
class Organisation(models.Model):
organisation_name = models.CharField(max_length=255, blank=True, null=True)
positions = models.ManyToManyField(Person, through='Position')
# positions are dynamic, even though there should only be only one director and president at each given time, a onetoone model wouldn't work in this scenario
objects = OrganisationManager()
# The following defs are currently used to show the names and start dates of the director and president in the detailview and listview
def director(self):
return self.position_set.filter(position_current=True, position_type="director").last()
def president(self):
return self.position_set.filter(position_current=True, position_type="P").last()
class Position(models.Model):
POSITION_TYPES = (
('president','President'),
('director','Director'),
)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE)
position_type = models.CharField(max_length=255, choices=POSITION_TYPES, blank=True, null=True)
position_current = models.BooleanField(default=False)
position_start = models.CharField(max_length=10, blank=True, null=True)
I want my table to look like this:
Organisation Name
President
President Start Date
Director
Director Start Date
Organisation 1
President of org 1
2013
Director of org 1
2015
Organisation 2
President of org 2
2018
Director of org 2
2017
With the code I currently have, it all works great. But because it has to call the database each time, this even causes Heroku to timeout.
I don't understand how to use the prefetch query in the models.Manager in the table (ListView) template. Thanks!
One approach to achieve the results you want is to use subqueries. So something like:
president_subquery = Position.objects.filter(
organisation=OuterRef('pk'), position_type='president', position_current=True
).last()
director_subquery = Position.objects.filter(
organisation=OuterRef('pk'), position_type='director', position_current=True
).last()
Organisation.objects.annotate(
president=Subquery(president_subquery.values('person__fullname')),
president_start_date=Subquery(president_subquery.values('position_start')),
director=Subquery(director_subquery.values('person__fullname')),
director_start_date=Subquery(director_subquery.values('position_start')),
)

Django Joining Tables

I am trying to get the information from one table filtered by information from another table (I believe this is called joining tables).
I have these two models:
class Listing(models.Model):
title = models.CharField(max_length=64)
description = models.CharField(max_length=500)
price = models.DecimalField(max_digits=11, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))])
category = models.ForeignKey(Category, on_delete=models.CASCADE, default="")
imageURL = models.URLField(blank=True, max_length=500)
creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name="creator", default="")
isOpen = models.BooleanField(default=True)
def __str__(self):
return f"{self.id} | {self.creator} | {self.title} | {self.price}"
class Watchlist(models.Model):
listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="listingWatched", default="")
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="userWatching", default="")
What I need to do is to get all the listings from a specific user Watchlist, the idea is to generate a page with all of the information of each of the listings that are in the user's watchlist. What should I do?
Thanks in advance!
Since is a foreign key, in Django you can access the information by calling the attribute
For example:
my_user = Watchlist.objects.get(pk=1)
print(my_user.listing.title)
You can also access to that attrbute in a query in case you need to filter upwards some value
values = Watchlist.objects.all().filter(listing__title='MyTitle')
my_titles = [x.title for x in values]
print(my_titles)
Or in your case, if you want to list all the title for a specific user
values = Watchlist.objects.all().filter(user='foo_user')
my_titles = [x.listing.title for x in values]
print(my_titles)
More documentation here

How to specify GROUP BY field in Dajngo ORM?

I have the following working SQL statement:
SELECT id FROM ops_kpitarget WHERE (site_id = 1 AND validFrom <= "2019-08-28") GROUP BY kpi_id HAVING validFrom = (MAX(validFrom))
But I cannot get this to work inside Django ORM.
The best I got was the code below, but then the database is complaining that it is missing a GROUP BY clause to make HAVING work.
How can I get the same query with specifying "kpi_id" as the GROUP BY clause using Djangos ORM? Any ideas?
KpiTarget.objects
.filter(validFrom__lte=fromDate)
.values("id", "kpi")
.filter(validFrom=Max("validFrom"))
... which translates to:
SELECT "ops_kpitarget"."id", "ops_kpitarget"."kpi_id" FROM "ops_kpitarget" WHERE "ops_kpitarget"."validFrom" <= 2019-08-14 HAVING "ops_kpitarget"."validFrom" = (MAX("ops_kpitarget"."validFrom"))
I played around with annotate but this is not really giving me what I want...
Update:
Some background: I have 3 tables: Kpi, KpiTarget, and KpiTargetObservation.
Kpi holds all general information regarding the KPI like name, typeetc.
KpiTarget stores target values defined for several different sites. These target values can change over time. Hence, I have included the combination of MAX() and validFrom <= (some date) to determine the latest valid target for any given KPI.
KpiTargetObservation stores the individual observations per defined KPI target. It just holds the link to KpiTarget, the date of the observation, and the observation value.
The final queries I need to build will have to give me something like the following:
give me all known KPIs per given site
tell me the most recent target value for the KPIs you found
get me any known observation that is related to the identified kpi targets
I am struggling with the 2nd query, and specifically how to get this working using Djangos ORM. I could just escape to RAW SQL, but I would prefer to not to, if possible.
The models:
class KpiCategory(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Kpi(models.Model):
KPI_KIND_CHOICES = [("BOOL", "Boolean"), ("FLOAT", "Float"), ("STRING", "String")]
# firstCreated = models.DateField(auto_now_add=True)
# firstCreatedBy = models.OneToOneField(User, on_delete=models.CASCADE)
# lastEdited = models.DateField(auto_now=True)
# lastEditedBy = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
category = models.ForeignKey(KpiCategory, on_delete=models.CASCADE)
kind = models.CharField(max_length=150, choices=KPI_KIND_CHOICES)
def __str__(self):
return self.name
class KpiTarget(models.Model):
# firstCreated = models.DateField(auto_now_add=True)
# firstCreatedBy = models.OneToOneField(User, on_delete=models.CASCADE)
# lastEdited = models.DateField(auto_now=True)
# lastEditedBy = models.OneToOneField(User, on_delete=models.CASCADE)
kpi = models.ForeignKey(Kpi, on_delete=models.CASCADE, related_name="kpiTargetSet")
targetDouble = models.DecimalField(
max_digits=20, decimal_places=15, blank=True, null=True
)
targetBool = models.BooleanField(blank=True, null=True)
targetStr = models.CharField(max_length=255, blank=True)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
validFrom = models.DateField()
def __str__(self):
return str(self.kpi)
class KpiObservation(models.Model):
# firstCreated = models.DateField(auto_now_add=True)
# firstCreatedBy = models.OneToOneField(User, on_delete=models.CASCADE)
# lastEdited = models.DateField(auto_now=True)
# lastEditedBy = models.OneToOneField(User, on_delete=models.CASCADE)
kpiTarget = models.ForeignKey(
KpiTarget, on_delete=models.CASCADE, related_name="kpiObservationSet"
)
observed = models.DateField()
observationDouble = models.DecimalField(
max_digits=20, decimal_places=15, blank=True, null=True
)
observationBool = models.BooleanField(blank=True, null=True)
observationStr = models.CharField(max_length=255, blank=True)
def __str__(self):
return str(self.observed)
KpiTarget.objects.filter(validFrom__lte=fromDate).annotate(validFrom=Max("validFrom")).order_by('kpi__id').values("id", "kpi")

Django : pre selection or tags. model relations

Django Version is 2.1.7
Hello, i have a OneToMany Relation and i ask my self if there is a possibility to make some kind of pre-selection (Tags or so?) for my Farmers?
Because not every Farmer has or wants Chickens or he is specialist in Cows only.
Means, right now, whenever i want to assign an individual Animal to a Farmer, i see all Farmers displayed in my Django Admin. With a growing Number of Farmers it gets confusing. So i thought to insert some Kind of Model Field in my Farmers Model... like chickens = true or not true and cows = true or not true or to introduce a new model for every species.
My Goal is, to assign a set of species to a every farmer. So that the Next time i want to add a chicken django shows only Farmers that will work with Chickens on their Farmland, it makes no sense to Display all Farmers, when some Farmers know that they handel only a specific set of species.
As a Newbie i would guess i have to make some new models for every Species with a ManyToMany Relation? So Farmers >< Species X, Y, Z < Indiviual Anmial.
Thanks
class Farmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
<...>
class Chickens(models.Model):
farmer = models.ForeignKey(Farmers, on_delete=models.CASCADE, null=True)
chickenname = models.CharField(max_length=100)
<...>
class Cows(models.Model):
farmer = models.ForeignKey(Farmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
<...>
class Rabbits(models.Model):
farmer = models.ForeignKey(Farmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
<...>
If we are using postgres as DB then arrayFieldlink
can be a good option for doing this job.
from django.contrib.postgres.fields import ArrayField
class Farmers(models.Model):
.... necessary fields
SAMPLE_CHOICES = (
('CHICKEN', 'CHICKEN'),
('COW, 'COW'),
('No Species', 'No Species')
.....
)
choices = ArrayField(
models.CharField(choices=SAMPLE_CHOICES, max_length=10, blank=True, default='No Species'),
)
Now whenever we need to filter on Farmer model based on choices we can do this like following
Farmer.objects.filter(choices__contains=['cow'])
Update
As you are using django-mysql database, following thing by django-mysql link here we can have field feature like ListField link and can easily achieve this.
class ChickenFarmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
class CowFarmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
class RabbitsFarmers(models.Model):
name = models.CharField(max_length=100)
farm_img = models.ImageField(upload_to='farm/', max_length=255, null=True, blank=True)
slug_farm = models.SlugField(blank=True)
class Chickens(models.Model):
farmer = models.ForeignKey(ChickenFarmers, on_delete=models.CASCADE, null=True)
chickenname = models.CharField(max_length=100)
class Cows(models.Model):
farmer = models.ForeignKey(CowFarmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
class Rabbits(models.Model):
farmer = models.ForeignKey(RabbitsFarmers, on_delete=models.CASCADE, null=True)
cowname = models.CharField(max_length=100)
'''
I think at this point this will give you best relief
'''