Ordering a django model on many-to-may field. Denormalization required? - python

I have a system for composing items from parts in certain categories
For instance take the following categories:
1: (Location)
2: (Material)
And the following parts:
Wall (FK=1)
Roof (FK=1)
Roof (FK=1)
Brick (FK=2)
Tile (FK=2)
Wood (FK=2)
To compose these items:
Wall.Brick, Roof.Wood, Wall.Wood
class Category(models.Model):
ordering = models.IntegerField()
desc = models.CharField()
class Part:
name = models.CharField()
category = models.ForeignKey(Category)
class Meta:
unique_together = ('name', 'category')
ordering = ['category','name']
class Item:
parts = ManyToManyField(Part)
def __unicode__(self):
return ".".join([p.name for p in self.parts.all()])
Now the question: how do i order the Items? I'd prefer to have them ordered ascending by the composed name, but dont know how.
One way of doing things could be an extra field for the name, that gets updated on the save() method. That would mean denormalizing the model...

If I understand correctly, sort key do not exist in database, so database cannot sort it (or at least on trivially, like using Django ORM).
Under those conditions, yes - denormalize.
It's no shame. As said, normalized dataset is for sissies...

Related

How to reduce quantity of an item in main table when it is being used in another table - django

I am creating my model in Django and I have a many to many relationship between supplies and van kits. The idea is that an "item" can belong to many "van kits" and a "van kit" can have many " items. I created an intermediary model that will hold the relationship, but I am struggling to figure out a way to relate the quantity in the van kit table to the quantity in the main supplies table. For example, if I wanted to mark an item in the van kit as damaged and reduce the quantity of that supply in the van kit, I would also want to reduce the total count of that supply in the main "supplies" table until it has been replenished. I am thinking that maybe I'll have to create a function in my views file to carry out that logic, but I wanted to know if it could be implemented in my model design instead to minimize chances of error. Here's my code:
class supplies(models.Model):
class Meta:
verbose_name_plural = "supplies"
# limit the user to selecting a pre-set category
choices = (
('CREW-GEAR','CREW-GEAR'),
('CONSUMABLE','CONSUMABLE'),
('BACK-COUNTRY','BACK-COUNTRY')
)
supplyName = models.CharField(max_length=30, blank=False) # if they go over the max length, we'll get a 500 error
category = models.CharField(max_length=20, choices = choices, blank=False)
quantity = models.PositiveSmallIntegerField(blank=False) # set up default
price = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) # inputting price is optional
def __str__(self):
return self.supplyName
class van_kit(models.Model):
supply_name = models.ManyToManyField(supplies, through='KitSupplies',through_fields=('vanKit','supplyName'), related_name="supplies")
van_kit_name = models.CharField(max_length=100)
vanName = models.ForeignKey(vans, on_delete=models.CASCADE)
def __str__(self):
return self.van_kit_name
class KitSupplies(models.Model):
supplyName = models.ForeignKey(supplies, on_delete=models.CASCADE)
vanKit = models.ForeignKey(van_kit, on_delete=models.CASCADE)
quantity = models.PositiveSmallIntegerField(blank=False)
def __str__(self):
return str(self.supplyName)
class Meta:
verbose_name_plural = 'Kit Supplies'
I am fairly new to django, I have to learn it for a class project so if my logic is flawed or if a better way to do it is obvious, please respectfully let me know. I'm open to new ways of doing it. Also, I've read through the documentation on using "through" and "through_fields" to work with the junction table, but I'm worried I may not be using it correctly. Thanks in advance.
One option would be to drop/remove the field quantity from your supplies model and just use a query to get the total quantity.
This would be a bit more expensive, as the query would need to be run each time you want to know the number, but on the other hand it simplifies your design as you don't need any update logic for the field supplies.quantity.
The query could look as simple as this:
>>> from django.db.models import Sum
>>> supplies_instance.kitsupplies_set.aggregate(Sum('quantity'))
{'quantity__sum': 1234}
You could even make it a property on the model for easy access:
class supplies(models.Model):
...
#property
def quantity(self):
data = self.kitsupplies_set.aggregate(Sum('quantity'))
return data['quantity__sum']

Dynamic field value in Django class

I want to create one dynamic field value for my class in Django using PyCharm.
CATEGORY_CHOICES = (
('on','one'),
('tw','two'),
('th','three'),
('fo','four'),
('fi','five'),
)
class art(models.Model):
Title=models.CharField(max_length=300)
Desciption=models.TextField()
Category=models.CharField(max_length=2, choices=CATEGORY_CHOICES)
I want the category field in my class to take more than one option, maybe two or more.
Any help would be appreciated.
If you want one python model to have multiple categories, then you need django ManyToManyField. Basically one model object could have multiple choices, one choice can also belong to multiple models objects:
class Category(models.Model):
category_name = models.CharField(max_length=10, unique=True)
class Art(models.Model):
title = models.CharField(max_length=300)
description = models.TextField()
category = models.ManyToManyField('Category', blank=True)
Note that I put unique=True for category_name to avoid creating duplicate categories.
Something not related, you shouldn't use lower fist in model name, and upper first for field name, that's really BAD naming convention and might confuse others who read your code.
Example:
# create your category in code or admin
one = Category.objects.create(category_name='one')
two = Category.objects.create(category_name='two')
three = Category.objects.create(category_name='three')
# create a new art obj
new_art = Art.objects.create(title='foo', description='bar')
# add category to Art obj
new_art.category.add(one)
new_art.category.add(two)
# category for new art obj
new_art_category = new_art.category.all()
# get only a list of category names
category_names = new_art_category.values_list('category_name', flat=True)
# create another Art obj
new_art2 = Art.objects.create(title="test", description="test")
# assign category to new_art2
new_art2.category.add(two)
new_art2.category.add(three)
Django doc for many to many and python pep8 doc.

Ordering case insensitive in django-tables2

First post in this awesome community that I have been reading a long time ago :)
I've encountered with a problem when using this fantastic library "django-tables2". When I'm ordering a column by a CharField, it does a case sensitive ordering resulting unexpected behaviour like this:
Iago
Pablo
iago
I want to be ordered in a more natural way:
Iago
iago
Pablo
This is my simplified code for the table:
class Inquiry(models.Model):
...
contact_last_name = models.CharField(max_length=50)
...
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name'))
class Meta:
model = Inquiry
fields= ('contact_last_name',)
I know in Django 1.8 there is a built-in function Lower to make a insensitive order_by, but it doesn't work with django tables:
contact_last_name = tables.Column(verbose_name="Contact", order_by=(Lower('contact_last_name')))
It results in an exception:
TypeError at /
'Lower' object is not iterable
Has anyone done anything similar with django-tables2?
Thank you!
UPDATE: The solution is to make a annotation in the view with the lowercase fields, an then will be available to order by in the table.
class Inquiry(models.Model):
...
contact_last_name = models.CharField(max_length=50)
...
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name_lower'))
class Meta:
model = Inquiry
fields= ('contact_last_name',)
And make the proper annotation in the queryset when configuring the table as Alasdair purposed:
inquiries = inquiries.annotate(contact_last_name_lower=Lower('last_name'))
my_table = Hometable(inquiries)
Based on https://django-tables2.readthedocs.io/en/latest/pages/ordering.html#table-order-foo-methods you need to add a table.order_FOO() method and end up with something like this:
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name'))
def order_contact_last_name (self, QuerySet, is_descending):
QuerySet = QuerySet.annotate(
field_lower=Func(F('contact_last_name '), function='LOWER')
).order_by(('-' if is_descending else '') + 'field_lower')
return (QuerySet, True)
class Meta:
model = Inquiry
fields= ('contact_last_name',)
This should work when you click column headers to order as well as the initial ordering.
I haven't tried this, and I'm not really familiar with django-tables2 so I don't know whether it will work.
You could try using a new field name e.g. contact_last_name_lower when setting order_by for the column.
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name_lower',))
class Meta:
model = Inquiry
fields= ('contact_last_name',)
Then, when you instantiate the table, annotate the queryset with the lower case field.
queryset = Inquiry.objects.annotate(contact_last_name_lower=Lower('contact_last_name'))
table = Hometable(queryset)

Django.rest_framework: How to serialize one to many to many?

I have some troubles serializing with django.
I have three models, let's say a School, a Room and a Desk (dummy name for example).
Each School have multiple Room, and each Room have multiple Desk.
The classes and their relations look like this :
class School(models.Model):
name = models.CharField()
class Room(models.Model):
name = models.CharField()
school_id = models.ForeignKey(School)
class Desk(models.Model):
row = models.IntegerField()
col = models.IntegerField()
room_id = models.ForeignKey(Room)
I want to be able to serialize a list of School, each directly containing all the desks inside.
The closet I got was by writing in my serialize.py three serializer :
class DeskSerializer(serializers.ModelSerializer):
class Meta:
field = (row, col,)
class RoomSerializer(serializers.ModelSerializer):
desks = DeskSerializer(source='desk_set', many=True)
class Meta:
field = (desks,)
class SchoolSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(source='room_set', many=True)
class Meta:
field = (name, rooms,)
Which return a list of school containing a list of room containing a list of desk, when I want a list of School containing a list of desk
Which source should I use in the School serializer to return directly the desk? Something like source='room_set.desk_set'? Or maybe by using a transform_ function?
PS: the code is write from scratch on the post, please ignore the possible syntax errors
If I'm understanding you correctly, you want the SchoolSerializer to return a nested structure 2 levels deep, but skipping the intermediate model. To do this, I would create a method in your School model to retrieve the Desk instances:
class School(models.Model):
...
def get_desks(self):
rooms = Room.objects.filter(school=self)
desks = Desk.objects.filter(room__in=rooms)
return desks
Then, in your SchoolSerializer include a field that uses that method and renders the returned instances as you wish via your DeskSerializer:
class SchoolSerializer(serializers.ModelSerializer):
...
desks = DeskSerializer(
source='get_desks',
read_only=True
)
class Meta:
field = (name, desks)
The key to understanding how this works is that the model method used as the value for the source argument must simply return instances of that serializer's model. The serializer takes it from there, rendering the object(s) however you defined them within that serializer (in this case the DeskSerializer).
Hope this helps.

Better way to represent Many to many relationship in django admin

I have a unique problem the way it should be handled in django admin.
I have following models structure...
class Product(models.Model):
name = models.CharField(max_length = 100)
base_price = models.DecimalField(max_digits = 5, decimal_places = 2)
def __unicode__(self):
return self.name
class Country(models.Model):
name = models.CharField(max_length = 2)
base_price = models.DecimalField(max_digits = 5, decimal_places = 2)
def __unicode__(self):
return self.name
class CountryProduct(models.Model):
country = models.ForeignKey(Country)
product = models.ForeignKey(Product)
overriden_price = models.DecimalField(max_digits = 5, decimal_places = 2)
class Meta:
unique_together = (("country", "product"),)
As shown here there is many to many relationship between products and countries.... I want to provide admin interface for overriding base price for given country and product.
One option to have ui as follows, here dash (-) represents default price and value in number represents override price for given country and product.
countries -> | US | UK
products | |
---------------------------
Product1 | - | 10
Product2 | 5 | 7
But I don't know how to do that....
I am open to look at alternative approaches (including changes in model structure) as well as long as it meets the requirement... Your input of any sort will definitely be useful to me...
Thanks in Advance :)
I got the solution, here is my answer to my question... Let me share it with you... I changed the model in following way....
class Product(models.Model):
name = models.CharField(max_length = 100)
base_price = models.DecimalField(max_digits = 5, decimal_places = 2)
def __unicode__(self):
return self.name
class Country(models.Model):
name = models.CharField(max_length = 2)
base_price = models.DecimalField(max_digits = 5, decimal_places = 2)
products = models.ManyToManyField(Product, through = 'CountryProduct')
def __unicode__(self):
return self.name
class CountryProduct(models.Model):
country = models.ForeignKey(Country)
product = models.ForeignKey(Product)
overriden_price = models.DecimalField(max_digits = 5, decimal_places = 2)
class Meta:
unique_together = (("country", "product"),)
class CountryProductInline(admin.TabularInline):
model = CountryProduct
class CountryAdmin(admin.ModelAdmin):
inlines = [CountryProductInline]
class ProductAdmin(admin.ModelAdmin):
inlines = [CountryProductInline]
Though this is not the way I expected, this gives me even better solution....
There is no way built-in to the django admin to do what you need.
You could create your own custom view, and do it that way. You can add extra views to an admin.ModelAdmin class, that will do what you need.
This is -- potentially -- a terrible design. Your database table should contain the correct price.
Your application must now do two things. It must get a default price from somewhere else (not in this table) and it must also get an override price (from this table) and put the two pieces of information together.
You cannot trivially make SQL work with the kind of grid you are showing.
You cannot easily get the Django admin to work with a grid like you are showing. You can try to create a grid template, but it's unique to this many-to-many relationship, so you also have to customize the Django admin views to use your template for one many-to-many table, and use the ordinary default template for all other tables.
To create the grid you must fetch all of your countries and products. You must then create the appropriate list-of-lists. You can then write your own template to display this. After you have more than 12 or so countries, the grid will be so wide as to be nearly useless. But for the first few countries you can make this work.
You'll have to create your own template and your own view function to do this.
Edit
"I am open to look at alternative approaches (including changes in model structure) as well as long as it meets the requirement"
Which requirement? The poor design where it takes two queries to find the price? Is that required?
Or the very difficult grid layout? Is that required?
It's not clear what "the requirement" is, so it's not possible to propose any alternative. It's only possible to say
A SQL design that queries base and overrides separately will be slower and more complex.
A SQL design that has a single value which is loaded from a "dynamic default" and can be changed (or not) by the user is much, much simpler. This can be done with the initial argument. http://docs.djangoproject.com/en/dev/ref/forms/fields/#initial
SQL can't easily turn multiple rows into a grid-like structure. This requires either sophisticated SQL (well outside the ORM's capability) or Python processing in a view function.
The Django admin won't do grid-like structures at all.

Categories

Resources