Better way to represent Many to many relationship in django admin - python

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.

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.

Seeking help in designing django models

I am looking for some feedback on a django model for a project I'm working on.So I'm building a document database where the documents can be split into 3 categories - GTO,EWR and QPR.Each of these documents correspond to a well.Each well can have multiple documents associated with it.The users can upload and view documents corresponding to a well.Here's my design :
basedoc - class to hold document's attributes and will serve as base class.
wells - class to hold well's attributes.
GTO - inherits from basedoc and is linked with wells using foreign key.
EWR - inherits from basedoc and is linked with wells using foreign key.
QPR - inherits from basedoc and is linked with wells using foreign key.
class basedoc(models.Model):
docfile = models.FileField(upload_to='documents/%Y/%m/%d')
title = models.CharField("Doc Title",max_length=50)
pub_date = models.DateTimeField('Date published',auto_now_add=True)
remark = models.TextField(max_length=200,blank=True)
publisher = models.ForeignKey(User)
def __str__(self):
return self.title
class wells(models.Model):
well_name = models.CharField(max_length=20)
well_loc = models.CharField(max_length=20)
def __str__(self):
return self.well_name
class GTO(basedoc):
gto = models.ForeignKey(wells)
pass
class EWR(basedoc):
ewr = models.ForeignKey(wells)
pass
class QPR(basedoc):
qpr = models.ForeignKey(wells)
pass
I initially used basedoc as an abstract base class,but changed because i wanted to return a list of all documents to the user as a view.Please help me in improving this design.Thanks.
You probably need to retrieve all the documents of a wells from time to time. Or you may need to move a document from GTO to EWR. To be efficient with that, I wouldn't use 3 tables but 1.
You can use choices :
TYPE_CHOICES = (
(1, 'GTO'),
(2, 'EWR'),
(3, 'QPR'),
)
class Document(models.Model):
# ...your other fields here...
type = models.IntegerField(choices=TYPE_CHOICES)

Auto Incrementing natural keys with django / postgres

Let me preface this in saying that I'm a UI dev who's trying to branch out into more backend coding, so excuse me if my verbiage is off at all. This is could be a duplicate, but i'm not sure what on god's good green earth i'm even supposed to call what i want to do.
Basically, I have categories, and images. I need to label each image with an acronym of the category it belongs to, and increment a sku after.
For Example, the following images would be automatically labeled like...
ABC-1
ABC-2
DEF-1
DEF-2
DEF-3
ABC-3*
*note: I want it to increment the ID based on the category, not the total # of images
How would I achieve this in idiomatic Django?
Models:
class Group(models.Model):
title = models.CharField(max_length=200)
abbv = models.CharField(max_length=200)
urlified = models.CharField(max_length=200)
description = models.TextField(blank=True)
hidden = models.BooleanField()
def __unicode__(self):
return self.title
class Photo(models.Model):
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
class Meta:
ordering = ('pub_date',)
If you want true composed primary keys, you might want to use django-compositepks, but that is not ideal. You might be better off breaking DRY and recording the number (see the category_auto_key field and default).
Transactions will solve it this way:
from django.db import transaction
class Group(models.model):
# your fields
img_count = models.IntegerField()
#transaction.atomic
def next_sku(self):
self.img_count += 1
self.save()
return self.img_count
class Photo(models.Model):
# your fields
category_auto_key = models.IntegerField(editable=False)
def category_image(self):
return self.group.abbv+"-"+str(self.category_auto_key)
def save(self, *args, **kwargs):
if not self.category_auto_key:
self.category_auto_key = self.group.next_sku()
super(Photo, self).save(*args, **kwargs)
When you need this in your templates, just enclose it in double brackets:
{{ photo.category_image }}
I'm curious if you just want to generate and store the acronym and sku in a text field, or if you are trying to create relationships between your image categories?
If the later, I would look for a different approach.
If the former, i would use a customized set or save method (hook?) for your image model. It will need do a small one time lookup to count the number of acronym already existing, but I wouldn't worry about the performance too much.
Wasn't sure how to do this exactly in Django off the top of my head, but it looks like the accepted answer works similarly. Anyways, here is my attempt at setting a Model Field during save. Be warned this in untested.
After looking into it more I think that Beltiras' solution is better
class Photo(models.Model):
# simple column definitions
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
# new column for storing abbv sku
category_label = models.CharField(max_length=200)
# save override
def save(self, *args, **kwargs):
# hopefully only set category_label on first save, not sure this
# works, open to other ideas
if (self.pk is None):
count = Photo.objects.filter(group=self.group).count()
label = self.group.abbv + '-' + count
setattr(self, 'category_label', label)
# call the super class' save method
super(Photo, self).save(*args, ** kwargs)
The part I am least sure about is:
count = Photo.objects.filter(group=self.group).count()
The idea is to query the photos table for photos in the same group and count them. This may need to be replaced with a direct SQL call or done some other way. Let me know what you find.

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

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...

Categories

Resources