Django model configuration for dynamic attributes - django

I have been trying to figure out the best way (or correct) way to set up models for our PIM/PriceModel app in Django.
Example models (stripped):
class ProductItem(models.Model):
"""
An actual item/product, what it all revolves around.
"""
part_number = models.CharField(unique=True, max_length=50, help_text='')
internal_part_number = models.CharField(primary_key=True, max_length=50, help_text='') # prefilled by partnumber
type = models.ForeignKey('Type', null=True, on_delete=models.SET_NULL)
attributes = JSONField() # Or another dynamic field
# and more ....
class Type(models.Model):
"""
Product type i.e. camera-dome, camera-bullet, pir, etc.
"""
pass
class Segment(models.Model):
"""
A segment of the company like Industry, Retail, Guarding etc.
"""
pass
class ProductCategory(models.Model):
"""
Supposedly a child category of TopCategory.
"""
pass
class InstallPrice(models.Model):
"""
Product item installation prices based on Type, Product Category and Segment.
"""
install_price = models.DecimalField(max_digits=8, decimal_places=2, help_text='')
type = models.ForeignKey('Type', null=True, on_delete=models.SET_NULL)
product_category = models.ForeignKey('ProductCategory', null=True, on_delete=models.SET_NULL)
segment = models.ForeignKey('Segment', null=True, on_delete=models.SET_NULL)
Take a look at "attributes = HStoreField(db_index=True)" in the ProductItem model.
The main thing i need to store in a product item is attributes like how many inputs/outputs/connection-options does it have. I need to store this for testing products against each other further down the line in the price-model app. This to make sure you have the right amount of products with matching attributes like inputs or outputs. I also need the User/Admin to be able to add this attributes dynamically so the app becomes self sustainable and not requires a migration id there is a new attribute I dont yet know about.
As I could not figure out a reasonable model configuration I ended up looking at postgres specific fields. This is not a must!
ideally when selecting type in the admin section i would like a "preset" of attributes to be presented based on the type.
Attributes could be:
inputs # number
outputs # number
channels # number
analysis # Boolean
Is this achievable? Any suggestions are welcome as I have limited Data Base experience. I need help figuring out the models.

Related

How to get 2 different classes in 1 model field in django?

I am working in a project, where I have to merge/combine different classes into one field.
I want to do it that way, because I have two different classes ParkingArea1 and ParkingArea2 and I want to have the choice from the Admin and WebInterface, to select any of these two areas from a single drop-down list, and ideally store them in another's model field.
These are my models:
class ParkingArea1(models.Model):
zone_name = models.Charfield(max_length=255, unique=True)
# some more unique fields below
def __str__(self): return self.zone_name
class ParkingArea2(models.Model):
area_name = models.CharField(max_length=200, db_index=True, null=True, unique=True)
# some more unique fields below
def __str__(self): return self.area_name
class ChooseYourArea(models.Model):
area = # here I want a field, which should be able to hold any of the above classes
# some other stuff
How should I reform my models to achieve that?
NOTE: ParkingArea1 and ParkingArea2 are two actively used tables filled with data, so a change within these models wouldn't be optimal.
I researched a bit but couldn't find something to fit my case.
I understand that an abstract Base Class could be used for this case, but such a Base Class won't allow me to list all the areas that I want. Is there an obvious approach that I missed?
You can't if you want to use ForeignKeys. Both ParkingArea1 and ParkingArea2 have their own primary keys. Django won't know which table that foreign key belongs to. You need 2 fields.
There are ways around that but you need to ensure data consistency at the Django level, and the performance would be abysmal.
What I ended up doing, was to create another class ParkingAreas with two fields, where each field can hold either ParkingArea1 or ParkingArea2:
class ParkingAreas(models.Model):
area1 = models.OneToOneField(ParkingArea1, on_delete=models.SET_NULL, blank=True, null=True, default=None)
area2 = models.OneToOneField(ParkingArea2, on_delete=models.SET_NULL, blank=True, null=True, default=None)
def __str__(self):
if self.area1 is not None:
return self.area1.area1
else:
return self.area2.area2
class ChooseYourArea(models.Model):
area = models.ForeignKey(ParkingAreas, on_delete=models.SET_NULL, blank=True, null=True)
# some other stuff
Now I can get all parking areas in one place, even selectable with a drop-down menu, and store them in a ForeignKey field.
That way, when I need to access the areas, I can make some django queries and get what I need.

How to define models in django that provides custom values based on pre-selection for a field?

Given the following model that stores the user's wish list for reading books:
class ReadingList(models.Model):
user_id = models.ForeignKey(UserInfo, on_delete=models.DO_NOTHING, null=False, blank=False, default=None, db_column='user_id')
book= models.CharField(max_length=255, blank=False)
creation_time = models.DateTimeField(blank=True)
class Meta:
unique_together = (('user_id', book),)
I want to create a model that helps in tracking the time spent in the reading the book on different days which looks something like this:
class ReadingTracker(models.Model):
user_id = models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='user', blank=False, db_column='user_id')
book= models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='book-to-read', blank=False, db_column='book')
time = models.DateTimeField(blank=True)
time_spent = models.floatfield()
On the client-side (corresponding to ReadingTracker) for both the fields user_id and book
I see that ReadingList object (1), ReadingList object (2), ... are listed. But, this is not working as expected.
What I want to achieve are the following:
For user_id field I want to see the something like dummy_uid1, dummy_uid2, ... to be listed.
Consider dummy_uid1 wants to read book1 and book2 whereas dummy_uid2 wants to read book1 and book3.
When dummy_uid1 is selected as user_id, I want only book1 and book2 to be listed for selection.
How do I define the model in django rest framework to achieve this?
Any suggestions related to the above would be much appreciated and thank you in advance.
There are two parts to this question:
If you want to see a different value than ReadingList object (1) then you need to define the __str__ value of your model, you can do this like so:
class ReadingList(models.Model):
...
def __str__(self):
return f'{self.user_id}' # return whatever string you want to display
If you want to just display the books for a particular user then you can use a filter() (see the Django documentation):
reading_list = ReadingList.objects.get(...)
ReadingTracker.objects.filter(user_id=reading_list)
However, I would add that you have a user_id on your ReadingList object which does seem to connect to a User model, but your user_id on ReadingTracker is a ForeignKey relation to ReadingList, which is confusing. I would suggest renaming the field or actually making it link to the User model (though this is unnecessary as you can still filter by User through the ReadingList model).

Annotating values from filtered related objects -- Case, Subquery, or another method?

I have some models in Django:
# models.py, simplified here
class Category(models.Model):
"""The category an inventory item belongs to. Examples: car, truck, airplane"""
name = models.CharField(max_length=255)
class UserInterestCategory(models.Model):
"""
How interested is a user in a given category. `interest` can be set by any method, maybe a neural network or something like that
"""
user = models.ForeignKey(User, on_delete=models.CASCADE) # user is the stock Django user
category = models.ForeignKey(Category, on_delete=models.CASCADE)
interest = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)])
class Item(models.Model):
"""This is a product that we have in stock, which we are trying to get a User to buy"""
model_number = models.CharField(max_length=40, default="New inventory item")
product_category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL, verbose_name="Category")
I have a list view showing items, and I'm trying to sort by user_interest_category for the currently logged in user.
I have tried a couple different querysets and I'm not thrilled with them:
primary_queryset = Item.objects.all()
# this one works, and it's fast, but only finds items the users ALREADY has an interest in --
primary_queryset = primary_queryset.filter(product_category__userinterestcategory__user=self.request.user).annotate(
recommended = F('product_category__userinterestcategory__interest')
)
# this one works great but the baby jesus weeps at its slowness
# probably because we are iterating through every user, item, and userinterestcategory in the db
primary_queryset = primary_queryset.annotate(
recommended = Case(
When(product_category__userinterestcategory__user=self.request.user, then=F('product_category__userinterestcategory__interest')),
default=Value(0),
output_field=IntegerField(),
)
)
# this one works, but it's still a bit slow -- 2-3 seconds per query:
interest = Subquery(UserInterestCategory.objects.filter(category=OuterRef('product_category'), user=self.request.user).values('interest'))
primary_queryset = primary_queryset.annotate(interest)
The third method is workable, but it doesn't seem like the most efficient way to do things. Isn't there a better method than this?

Django model queryset using related names and managers

I have 4 models -
class Group(models.model):
group_id = models.CharField(max_length=10)
name = models.CharField(max_length=20)
class User(models.model):
user_id = models.CharField(max_length=10)
grp = models.ForeignKey(Group, null=True, blank=True)
user_name = models.CharField(max_length=50)
contact_no = models.CharField(max_length=20)
class DesigType(models.model):
desig_name = models.CharField(max_length=10)
class Designation(models.model):
user = models.ForeignKey(User, null=True, blank=True, related_name='desigs')
desig_type = models.ForeignKey(DesigType, null=True, blank=True, related_name='desigs')
Group model holds groups of users. User holds records for each individual user. DesigType has information about "type of designation", like maybe manager, team lead etc. Designation stores the exact designation - for example, for manager DesigType, Designation might have project manager or account manager; similarly, for team lead DesigType, Designation might have front-end lead or back-end lead.
The UI currently shows a list of users under a group. I want to implement a search functionality according to desig_name. The UI sends me the group_id and the text entered by the end-user in the search box, and I have to return only those Users which have the corresponding desig_name.
I have already done the above, by using a property to return a list of desig_names that a User has and checking whether the user input exists in the list.
This is a property under User -
#cached_property
def desig_types(self):
desig_types = []
for value in self.desigs.select_related('desig_type').values('desig_type__desig_name'):
desig_types.append(value['desig_type__desig_name'])
return desig_types
In my view I have a generic search function which takes any user filter to return the appropriate list of users.
group = Groups.objects.get(pk=grp_id)
_queryset = group.user_set.filter(**user_filter)
The above code works for filtering according to contact_no and user_name. For contact_no, **user_filter is group.user_set.filter(contact_no=user_input), and for user_name, it's group.user_set.filter(user_name=user_input). I want it to also work for filtering according to desig_name, but I couldn't figure out how to navigate through the relationships so I wrote the code below, that retrieves a list of user_ids which have the user inputted desig_name.
required_users = []
for user in group.user_set.all():
user_desig_names = user.desig_types
if user_input in user_desig_names:
required_users.append(user.user_id)
return required_users
I then pass in the filter as group.user_set.filter(user_id__in=required_users). But as you see, I have to have an additional code to get the user_ids, instead of directly using **user_filter, like with user_name or contact_no.
Does anyone know how I can do that?

Django (Model)Form Field: Manytomany with key value pair

I have a situation where I need to do something similar to rendering a formset within a formset. But I'd rather focus on the problem before jumping to a solution.
In English first:
I'm creating a shipment from a warehouse.
Each shipment can contain multiple lines (unique combinations of product_type and package_type) with an item_count
However for each line there could be multiple "Packages" - a package_type of a product_type that has an item_count. Think of this as a batch.
The customer is only interested in seeing one line for each product_type/package_type
But we need to pull out the stock and correctly attribute the particular units from each batch to allow stock control, recall control etc to function. Therefore the dispatch staff IS interested in exactly which Packages are shipped.
Add to this the sales staff enter a SalesOrder that only specifies the product_type/package_type. They aren't interested in the Packages either. (Think putting in a forward order for next month - who knows what will be in stock then?).
Now the models (simplified for clarity):
class Package(models.Model):
create_date = models.DateField()
quantity = models.FloatField()
package_type = models.ForeignKey(PackageType, on_delete=models.PROTECT)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT)
class CheckOut(models.Model):
package = models.ForeignKey(Package, on_delete=models.PROTECT)
create_date = models.DateField()
quantity = models.FloatField()
class Shipment(models.Model):
sales_order = models.ForeignKey(SalesOrder, null=True, blank=True)
ship_date = models.DateField(default=date.today,
verbose_name='Ship Date')
class ShipmentLine(models.Model):
shipment = models.ForeignKey(Shipment, null=True, blank=True)
sales_order_line = models.ForeignKey(SalesOrderLine, null=True, blank=True)
quantity = models.FloatField(verbose_name='Quantity Shipped')
checkout = models.ManytoManyField(CheckOut)
I currently have it working well with the constraint of a 1:M relationship of CheckOut:ShipmentLine. However when changing this to a M:M, things get knarly form-wise.
In the 1:M version the Shipment form (plus formset for the ShipmentLines) looks like this:
class CreateShipmentForm(forms.ModelForm):
class Meta:
model = om.Shipment
contact = forms.ModelChoiceField(
queryset=om.Contact.objects.filter(is_customer=True, active=True),
label='Customer')
customer_ref = forms.CharField(required=False, label='Customer Reference')
sales_order = forms.ModelChoiceField(queryset=om.SalesOrder.objects.all(),
required=False, widget=forms.HiddenInput())
number = forms.CharField(label='Shipment Number', required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
class CreateShipmentLineForm(forms.ModelForm):
class Meta:
model = om.ShipmentLine
widgets = {
'checkout': forms.HiddenInput()
}
fields = ('package', 'quantity', 'id',
'sales_order_line', 'checkout')
id = forms.IntegerField(widget=forms.HiddenInput())
sales_order_line = forms.ModelChoiceField(
widget=forms.HiddenInput(), required=False,
queryset=om.SalesOrderLine.objects.all())
package = forms.ModelChoiceField(required=True, queryset=None) # queryset populated in __init__, removed for brevity
So for the 1:M, I could select a package, set the quantity and done.
For M:M, I will need to select product_type, package_type, and then 1 or more packages, AND for each package a quantity. (I'll be using JS in the form to filter these)
In my mind's eye I have a few possibilities:
create a (child) formset for the Packages and quantities and include in each line of the (parent) formset
create some sort of multi-field, multi-value matrix custom form field and use that
construct a modal dialog where the M:M stuff happens and somehow save the result to the form where validation, saving happens.
I hope I have explained it correctly and clearly enough. It's the most complex application of Django forms I've encountered and I'm not sure what the limitations/pros/cons of each of my options is.
Has anyone encountered this situation and have a solution? Or any words to the wise?
My thanks in advance,
Nathan
I have a similar situation, I am doing something like your second and third options:
I have overridden __init__() and, after calling super, I have a loop that adds a value selector for every field (of course you could use a single custom element here)
Then override save() and after calling super I process the extra field adding all the values.