Creating New Model Form Fields in Django Admin - django

I have a product model that has a manytomany relationship to a locations model. I am creating an app for my clients business that has hundreds of products and services, but each product/service have different prices base on the delivery location and can deliver to multiple locations. Right now my client delivers to 4 locations.
Solution #1
Hard code all 4 locations into the product model - this works, but is not preferred since they want to expand and hard coding more locations is just gross..
Solution #2 (current solution - code listed below)
Create a manytomany relationship to locations - this works, but is getting way out of hand having location options of varying charges an rates for - multiplied by every product....
Solution #3 - This is the help I need, if a solution exists.
I would like to build a hybrid of sort of the above two options. Id like to keep the manytomany with the location model so its easy to add locations as they grow, but once added, I would like to have an empty 'price' object that they can fill-in when adding or updating a product, yet remain assigned to that product only.
Not sure if this makes sense, so after my current code below (solution 2 above) I included a sample image to help illustrate my question. Thank you for your help.
Product Model
class Product(models.Model):
...
locations = models.ManyToManyField('Location', related_name='deliver_to')
...
Location Model
LOCATIONS = (
('Los Angeles', 'Los Angeles'),
('Orange County', 'Orange County'),
('Riverside', 'Riverside'),
('San Diego', 'San Diego')
)
class Location(models.Model):
l_title = models.CharField(
max_length=255,
choices=LOCATIONS,
verbose_name='Service Location'
)
...

Using https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany will allow for extra fields.

Related

Modern methods for filtering a Django annotation?

I'd like to filter an annotation using the Django ORM. A lot of the articles I've found here at SO are fairly dated, targeting Django back in the 1.2 to 1.4 days:
Filtering only on Annotations in Django - This question from 2010 suggests using an extra clause, which isn't recommended by the official Django docs
Django annotation with nested filter - Similar suggestions are provided in this question from 2011.
Django 1.8 adds conditional aggregation, which seems like what I might want, but I can't quite figure out the syntax that I'll eventually need. Here are my models and the scenario I'm trying to reach (I've simplified the models for brevity's sake):
class Project(models.Model):
name = models.CharField()
... snip ...
class Milestone_meta(models.Model):
name = models.CharField()
is_cycle = models.BooleanField()
class Milestone(models.Model):
project = models.ForeignKey('Project')
meta = models.ForeignKey('Milestone_meta')
entry_date = models.DateField()
I want to get each Project (with all its fields), along with the Max(entry_date) and Min(entry_date) for each associated Milestone, but only for those Milestone records whose associated Milestone_meta has the is_cycle flag set to True. In other words:
For every Project record, give me the maximum and minimum Milestone entry_dates, but only when the associated Milestone_meta has a given flag set to True.
At the moment, I'm getting a list of projects, then getting the Max and Min Milestones in a loop, resulting in N+1 database hits (which gets slow, as you'd expect):
pqs = Projects.objects.all()
for p in pqs:
(theMin, theMax) = getMilestoneBounds(p)
# Use values from p and theMin and theMax
...
def getMilestoneBounds(pid):
mqs = Milestone.objects.filter(meta__is_cycle=True)
theData = mqs.aggregate(min_entry=Min('entry_date'),max_entry=Max('entry_date'))
return (theData['min_entry'], theData['max_entry'])
How can I reduce this to one or two queries?
As far as I know, you can not get all required project objects in one query.
However, if you don't need the objects and can work with just their id, one way would be-
Milestone.objects.filter(meta__is_cycle=True).values('project').annotate(min_entry=Min('entry_date')).annotate(max_entry=Max('entry_date'))
It will give a list of dicts having data of distinct projects, you can then use their 'id' to lookup the objects when needed.

How to generate Sum (and other aggregates) in Django where aggregate depends on values from related tables

My model consists of a Portfolio, a Holding, and a Company. Each Portfolio has many Holdings, and each Holding is of a single Company (a Company may be connected to many Holdings).
Portfolio -< Holding >- Company
I'd like the Portfolio query to return the sum of the product of the number of Holdings in the Portfolio, and the value of the Company.
Simplified model:
class Portfolio(model):
some fields
class Company(model):
closing = models.DecimalField(max_digits=10, decimal_places=2)
class Holding(model):
portfolio = models.ForeignKey(Portfolio)
company = models.ForeignKey(Company)
num_shares = models.IntegerField(default=0)
I'd like to be able to query:
Portfolio.objects.some_function()
and have each row annotated with the value of the Portfolio, where the value is equal to the sum of the product of the related Company.closing, and Holding.num_shares. ie something like:
annotate(value=Sum('holding__num_shares * company__closing'))
I'd also like to obtain a summary row, which contains the sum of the values of all of a user's Portfolios, and a count of the number of holdings. ie something like:
aggregate(Sum('holding__num_shares * company__closing'), Count('holding__num_shares'))
I would like to do have a similar summary row for a single Portfolio, which would be the sum of the values of each holding, and a count of the total number of holdings in the portfolio.
I managed to get part of the way there using extra:
return self.extra(
select={
'value': 'select sum(h.num_shares * c.closing) from portfolio_holding h '
'inner join portfolio_company as c on h.company_id = c.id '
'where h.portfolio_id = portfolio_portfolio.id'
}).annotate(Count('holding'))
but this is pretty ugly, and extra seems to be frowned upon, for obvious reasons.
My question is: is there a more Djangoistic way to summarise and annotate queries based on multiple fields, and across related tables?
These two options seem to move in the right direction:
Portfolio.objects.annotate(Sum('holding__company__closing'))
(ie this demonstrates annotation/aggregation over a field in a related table)
Holding.objects.annotate(Sum('id', field='num_shares * id'))
(this demonstrates annotation/aggregation over the product of two fields)
but if I attempt to combine them: eg
Portfolio.objects.annotate(Sum('id', field='holding__company__closing * holding__num_shares'))
I get an error: "No such column 'holding__company__closing'.
So far I've looked at the following related questions, but none of them seem to capture this precise problem:
Annotating django QuerySet with values from related table
Product of two fields annotation
Do I just need to bite the bullet and use raw / extra? I'm hoping that Django ORM will prove the exception to the rule that ORMs really only work as designed for simple queries / models, and anything beyond the most basic ones require either seriously gnarly tap-dancing, or stepping out of the abstraction, which somewhat defeats the purpose...
Thanks in advance!

Django Multiple Levels Choices for Field

I have a model that I would like to use the choices= option for, but three levels deep.
class Doctor(models.Model):
...
zipcode = models.CharField(max_length=10, choices=AREAS, null=True, blank=True)
Within the "zipcode" dropdown in the admin, I would like the hierarchy to be:
Bronx
--Kingsbridge
----10463
----10471
--Fordham
----10458
----10467
----10468
Brooklyn
--Borough Park
----11204
etc.
Then, if I choose zip code 10463, the Doctor object will be associated with the Kingsbridge area in the Bronx. I'm trying this a variety of different ways. The closest I've come is using this:
AREAS = (
('Bronx', (('Kingsbridge', ('10463', '10463'),),)),
...
)
Unfortunately, that gives me this hierarchy:
Bronx
--('10463', '10463')
which is weird and not helpful. Can anybody see where I'm going wrong? Is this hierarchy possible? Would it be smarter to just create another table in the app called Areas and use a manytomany field? The more I think about it, the more I think I have to use a manytomany field. Thanks in advance
As per https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.Field.choices,
it seems to be only 2 level hierarchy is supported. If you need more than 2 levels, you need to use either custom widgets (or) multiple fields with Foreign Key relationships.

How to model lending items between a group of companies

I have a group of related companies that share items they own with one-another. Each item has a company that owns it and a company that has possession of it. Obviously, the company that owns the item can also have possession of it. Also, companies sometimes permanently transfer ownership of items instead of just lending it, so I have to allow for that as well.
I'm trying to decide how to model ownership and possession of the items. I have a Company table and an Item table.
Here are the options as I see them:
Inventory table with entries for each Item - Company relationship. Has a company field pointing to a Company and has Boolean fields is_owner and has_possession.
Inventory table with entries for each Item. Has an owner_company field and a possessing_company field that each point to a Company.
Two separate tables: ItemOwner and ItemHolder**.
So far I'm leaning towards option three, but the tables are so similar it feels like duplication. Option two would have only one row per item (cleaner than option one in this regard), but having two fields on one table that both reference the Company table doesn't smell right (and it's messy to draw in an ER diagram!).
Database design is not my specialty (I've mostly used non-relational databases), so I don't know what the best practice would be in this situation. Additionally, I'm brand new to Python and Django, so there might be an obvious idiom or pattern I'm missing out on.
What is the best way to model this without Company and Item being polluted by knowledge of ownership and possession? Or am I missing the point by wanting to keep my models so segregated? What is the Pythonic way?
Update
I've realized I'm focusing too much on database design. Would it be wise to just write good OO code and let Django's ORM do it's thing?
Is there a reason why you don't want your item to contain the relationship information? It feels like the owner and possessor are attributes of the item.
class Company(models.Model):
pass
class Item(models.Model):
...
owner = models.ForeignKey(Company, related_name='owned_items')
holder = models.ForeignKey(Company, related_name='held_items')
Some examples:
company_a = Company.objects.get(pk=1)
company_a.owned_items.all()
company_a.held_items.all()
items_owned_and_held_by_a=Items.objects.filter(owner=company_a, holder=company_a)
items_on_loan_by_a=Items.objects.filter(owner=company_a).exclude(holder=company_a)
#or
items_on_loan_by_a=company_a.owned_items.exclude(holder=company_a)
items_a_is_borrowing=Items.objects.exclude(owner=company_a).filter(holder=company_a)
#or
items_a_is_borrowing=company_a.held_items.exclude(owner=company_a)
company_b = Company.objects.get(pk=2)
items_owned_by_a_held_by_b=Items.objects.filter(owner=company_a, holder=company_b)
#or
items_owned_by_a_held_by_b=company_a.owned_items.filter(holder=company_b)
#or
items_owned_by_a_held_by_b=company_b.held_items.filter(owner=company_a)
I think if your items are only owned by a single company and held by a single company, a separate table shouldn't be needed. If the items can have multiple ownership or multiple holders, a m2m table through an inventory table would make more sense.
class Inventory(models.Model):
REL = (('O','Owns'),('P','Possesses'))
item = models.ForeignKey(Item)
company = models.ForeignKey(Company)
relation = models.CharField(max_length=1,choices=REL)
Could be one implementation, instead of using booleans. So I'd go for the first. This could even serve as an intermediate table if you ever decide to use a 'through' to relate items to company like this:
Company:
items = models.ManyToManyField(Item, through=Inventory)
Option #1 is probably the cleanest choice. An Item has only one owner company and is possessed by only one possessing company.
Put two FK to Company in Item, and remember to explicitly define the related_name of the two inverses to be different each other.
As you want to avoid touching the Item model, either add the FKs from outside, like in field.contribute_to_class(), or put a new model with a one-to-one rel to Item, plus the foreign keys.
The second method is easier to implement but the first will be more natural to use once implemented.

Query for a ManytoMany Field with Through in Django

I have a models in Django that are something like this:
class Classification(models.Model):
name = models.CharField(choices=class_choices)
...
class Activity(models.Model):
name = models.CharField(max_length=300)
fee = models.ManyToManyField(Classification, through='Fee')
...
class Fee(models.Model):
activity = models.ForeignKey(Activity)
class = models.ForeignKey(Classification)
early_fee = models.IntegerField(decimal_places=2, max_digits=10)
regular_fee = models.IntegerField(decimal_places=2, max_digits=10)
The idea being that there will be a set of fees associated with each Activity and Classification pair. Classification is like Student, Staff, etc.
I know that part works right.
Then in my application, I query for a set of Activities with:
activities = Activity.objects.filter(...)
Which returns a list of activities. I need to display in my template that list of Activities with their Fees. Something like this:
Activity Name
Student Early Price - $4
Student Regular Price - $5
Staff Early Price - $6
Staff Regular Price - $8
But I don't know of an easy way to get this info without a specific get query of the Fees object for each activity/class pair.
I hoped this would work:
activity.fee.all()
But that just returns the Classification Object. Is there a way to get the Fee Object Data for the Pair via the Activities I already queried?
Or am I doing this completely wrong?
Considering michuk's tip to rename "fee" to "classification":
Default name for Fee objects on Activity model will be fee_set. So in order to get your prices, do this:
for a in Activity.objects.all():
a.fee_set.all() #gets you all fees for activity
There's one thing though, as you can see you'll end up doing 1 SELECT on each activity object for fees, there are some apps that can help with that, for example, django-batch-select does only 2 queries in this case.
First of all I think you named your field wrong. This:
fee = models.ManyToManyField(Classification, through='Fee')
should be rather that:
classifications = models.ManyToManyField(Classification, through='Fee')
as ManyToManyField refers to a list of related objects.
In general ManyToManyField, AFAIK, is only a django shortcut to enable easy fetching of all related objects (Classification in your case), with the association table being transparent to the model. What you want is the association table (Fee in your case) not being transparent.
So what I would do is to remove the ManyToManyField field from Activity and simply get all the fees related with the activity. And thenm if you need a Classification for each fee, get the Classification separately.