Problem Description
Suppose I have a database with multiple models running with a Django front-end.
One of the tables in the Inventory. The inventory consists of entries with the following specifications:
class InventoryItem(models.Model):
item_name = models.TextField(max_length=10) #apple, orange, cilantro, etc...
item_quantity = models.DecimalField(...)
The next model will be to describe what is made with those ingredients
class Product(models.Model):
product_name = models.TextField(...)
product_description = models.TextField(...)
The ProductItem model also needs to keep track of the ingredients taken from inventory by specifying the InventoryItem and the quantity used from that inventory item used.
Previous Experience
In a previous experience, I have done something similar with EntityFramework in C# with MySQL. The way I achieved that was using another table/model called RecipeElement, where each one of those would be foreign-keyed to a ProductItem entry. The RecipeElement model looked like the following:
class RecipeElement(models.Model):
inventory_item = models.ForeignKey(InventoryItem, on_delete = models.CASCADE)
quantity_used = models.DecimalField(...)
product_item = models.ForeignKey(ProductItem, on_delete = models.CASCADE)
The Issue
My issue with that approach in Django is twofold:
How would I retrieve the RecipeElement entries associated with a ProductItem entry
How would the user input the RecipeElement entries and the ProductItem entries on one page. (The number of RecipeElements for each ProductItem is not limited, but each RecipeElement is associated with only one ProductItem
I am using SQLite for the moment but plan to transfer to MySQL in the future, if that changes anything.
If you want to retrieve all the RecipeElement for a Product do something like:
ProductItem.objects.get(pk=1).recipeelement_set.all()
In the second issue you can add a recipeElement from a product using .add() or create() like:
ProductItem.objects.get(pk=1).recipeelement_set.add(your_recipe_element)
Related
I am trying to link venues to the products they supply. The products supplied are not unique to each venue.
As a result, Venue 1 and 2 could both provide Product A.
The outcome I am looking for is twofold:
when a Product is added to the database, there is an option to link it to an existing Venue
When looking at a venue in particular, I would like to have the list of all the product that can be supplied
Outcome 1. and current problem
I tried using Foreign Keys and ManyToManyFields but this only seems to add all the products available to the database to all the venues without leaving a choice.
This is what venue = models.ManyToManyField(Venue, blank=True, related_name="available_products") renders in the admin panel. In this example, by adding ManyToMany Field all Venues have been added to Product 1. Whereas I would like the possibility to add only specific venues (not all)
Outcome 2. and current problem
The second problem is obviously referring to Product from the Venue model. If I input a foreign key or any form of relation in it, Django gets upset and tells me Product is not defined.
I thought of creating a 3rd model, that could combine both Venue and Products, but it feels like there must be something more sophisticated that could done.
(edit: I replaced the FK by ManyToManyField as suggested by David Schultz)
class Venue(models.Model):
name = models.CharField(verbose_name="Name",max_length=100, null=True, blank=True)
class Product(models.Model):
name = models.CharField('Product Name', max_length=120, null=True)
venue = models.ManyToManyField(Venue, blank=True, related_name="available_products")
A ManyToManyField should in fact be perfect for what you want to do. It only associates those objects to one another for which relations have been explicitly created, e.g. in the admin or programmatically. The fact that your admin shows you all objects at once does not mean that they have been assigned, but just that they are available. In the list from your screenshot, selection can be done by Ctrl+Mouseklick, and when you then save the Product and reload the page, precisely the Venues you selected before should now again show up with a distinct background color – this means that they have indeed been saved.
Regarding your second problem: The argument related_name works differently than you apparently think: In your last line of code, you should rather write something like related_name="available_products", because related_name becomes the name of an attribute of your Venue instances, by which you can then access all Product objects that have been associated to that Venue object, e.g. like so: venue.available_products.all()
related_name works the same for ManyToManyField and ForeignKey.
You can define your ManyToManyField either on Product or on Venue; some more info is in the documentation page. So all in all, you should do something like:
class Venue(models.Model):
name = models.CharField(verbose_name="Name",max_length=100, blank=True)
class Product(models.Model):
name = models.CharField('Product Name', max_length=120, blank=True)
venues = models.ManyToManyField(Venue, blank=True, related_name="available_products")
(Side note: For CharFields, it is recommended not to set null=True and instead only use blank=True, because otherwise there would be two different options for "no data", namely Null and an empy string. Details in the docs.)
My database models.py looks like this:
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
slug = models.SlugField(unique=True)
description = models.TextField()
quantity = models.IntegerField(default=1)
class Bike(models.Model):
item = models.OneToOneField(Item, on_delete=models.CASCADE)
image = models.ImageField(upload_to='bikes')
When I am deleting an entry in the table Item it also deletes everything that is associated in the table Bike. But when I am deleting an entry in the Bike table it doesn't delete any data in the Item table. Is there any possible way to delete all data in the Item table, when you delete an entry in the Bike table?
First, the behavior you describe (Bike is deleted when you delete Item) corresponds to the CASCADE argument you put in the field:
item = models.OneToOneField(Item, on_delete=models.CASCADE)
You should know, CASCADE is not the only behavior available, you can check them out there.
But I don't think any of them has the exact behavior you want.
To achieve this behavior you should use a pre_delete or post_delete signal. But in the end this is not a good solution in my opinion.
From what I read I understand that Bike and Item are not just related. They are the same thing. You shouldn't represent their relationship with a OneToOneField (from what I see). Bikeshould be a child of Item so that a Bike instance has every attribute of Itemand the attributes you want specifically for the a Bike. With this solution you'll find things more scalable and every use of your models will be more logical. You can learn more about model inheritance in the Django Documenation.
If I were wrong, please tell me.
Learning Django.. I am trying to setup the below and need your expert advise to make it better..
I created a model, and form on top of it to show the data.. and I have two questions after the code where I need your advise :)
Model:
class profilestable(models.Model):
choices = [
('Active','Active'),
('Inactive','Inactive')
]
Category = models.CharField(max_length=100)
SubCategory = models.CharField(max_length=100)
product_name = models.CharField(max_length=100)
Status = models.ChoiceField(max_length=20,choices=choices)
def __str__(self):
return self.Category
class Userlist(models.Model):
User = models.CharField(max_length=100)
Categorygroup = models.ForeignKey(profilestable,null=true,on_delete=models.SET_NULL)
def __str__(self):
return self.User
Form:
class userprofileform(forms.ModelForm):
model = Userlist
Fields = ('User','CategoryGroup')
def __init__(self,*args,**kwargs):
super(userprofileform,self).__init__(*args,**kwargs)
Sample data in the profilestable Model:
Queries:
I want to add the category, subcategory and product name as drop downs to the form however I am not sure how to access each element to show on the form. I was able to pull only category since I am returning that value.
The list currently has lots of duplicate values, is it possible to show only unique values in the drop down.
Also, is it possible to make it a multi-select and dependent cascading drop downs
Request you to please help advise/direct to implement this type of form.
Thank you so much.
The structure needs some changes, for example category should be a parent of sub-category, and then product lives below sub-category, so it would be better to create a separates models for Category, Sub-category and Product in this relation:
Category > Sub-category > Product Since sub-category and products are not unique relation to its parent, use the ForeignKey Field to connect them.
Also I see in your sample data and many products are shinked in 1 row, that is not good in any terms of scaling, performance or order.
Yet I cannot see the usecase of your Userlist model.
Once done the above please refer this answer to create your drop down menu, althought the distinct at the end is not needed as creating separate model for Category will avoid the need to duplicate Category names. Then on form submition, you Query for the correct Category and Sub-Category instance and assign it to the ForeignKey field of your form.
EDIT: Given responses in comments and answer I tried suggestion and I get some errors when trying to query , also doing the related name query does not get the right results (as seen in comments)
BusinessLocations.objects.all()
Error: QuerySet object has no attribute 'objects' is the error.
In either case, I did a dump of all the tables and see this:
auth_business_permissions', u'auth_permission', u'auth_user', u'auth_user_businesss', u'auth_user_user_permissions', u'django_admin_log',
u'django_content_type', u'django_migrations', u'django_session', u'ipaswdb_address', u'ipaswdb_billing', u'ipaswdb_billing_businesss',
u'ipaswdb_designation', u'ipaswdb_business', u'ipaswdb_business_business_locations', u'ipaswdb_businessinsurances', u'ipaswdb_businessinvoices',
'ipaswdb_businesslocations', u'ipaswdb_businessterm', u'ipaswdb_insurance', u'ipaswdb_insurance_businesss', u'ipaswdb_invoice', u'ipaswdb_employee',
u'ipaswdb_employeeinvoice', u'ipaswdb_employeelocations', u'ipaswdb_employeeterms', u'ipaswdb_specialty']
I have a ipaswdb_business_business_locations and a ipaswdb_businesslocations which seems strange to me, and I wonder if my database is just gunked up?
Original Question:
I have two models a Business and an Employee. I want them both to be aware of each other but not directly but through another model called a 'BusinessesLocation`. I can sort of express this in my models but it doesn't look or feel right. It is like only the employee knows of the businesses, and not vice vice versa.
I had another question opened to try to answer this but the answer was not 100% correct in that it didn't offer for a many to many it was more like a one to many. In this case: An employee can work at many locations (potentially being an employee of many businesses) and a business can have many locations having many employees.
Currently my models work where this shell script works:
someEmployee.business_locations.all()[0].business.business_name
and it works fine, I can get all the locations of a business an employee works at and via that infer the many businesses an employee might work for given the businesses locations.
But I cannot figure out how to go the other way, and find out all the employees a business has working for them and at which locations
My current (wrongish) models are like this:
class Employee(models.Model):
first_name = models.CharField(max_length = 50)
business_locations = models.ManyToManyField('BusinessLocations', through='EmployeeLocations')
class EmployeeLocations(models.Model):
employee = models.ForeignKey('Employee', on_delete=models.CASCADE)
business_location = models.ForeignKey('BusinessLocations', on_delete=models.CASCADE)
created_at=models.DateField(auto_now_add=True)
updated_at=models.DateField(auto_now=True)
def __str__(self):
return self.provider.first_name
class BusinessLocations(models.Model):
address = models.ForeignKey('Address', on_delete= models.SET_NULL, null=True)
business = models.ForeignKey('Business', on_delete=models.CASCADE)
doing_business_as = models.CharField(max_length = 255)
created_at=models.DateField(auto_now_add=True)
updated_at=models.DateField(auto_now=True)
def __str__(self):
return self.doing_business_as
class Business(models.Model):
business_name = models.CharField(max_length=50)
business_locations = I need something here no idea how
Bellow is some pseudo shell code demonstrating how I would like my models to work:
#create a new business location assume business has been created
newLocation = Address(...)
business.business_locations.add(newLocation, doing_business_as='alternative name maybe')
#assume employee exists
#add a new business location to the employee
#when i say selected business the form would have current employee then in its locations
#you'd have to select a business first, and get a list of all that businesses locations and you
#you could add the business location and then select another business with all ITS locations
# and add one there too if you wish
employee.employee_locations.add(selectedBusiness.business_locations[0])
employee.employee_locations.add(anotherSelectedBusiness.business_locations[1])
Below is what I cannot figure out how to do, vice versa...
#now lets see which businesses the employee works for.
for business in employee.business_locations
business.business_name
#and lets see each businesses employees:
for employee in Employee.objects.all()
employee.
?? No idea how to build the models to represent these relationships
I can get an employees business locations just fine, but I cannot get the above examples of getting a list of employees for a business. Not sure what I need to adjust (or methods I might need?) to get this to work like I want in my shell example.
What you're missing is Django's concept of related objects.
When you define a relationship in a model (i.e., a ForeignKey, OneToOneField, or ManyToManyField), instances of that model will have a convenient API to access the related objects.
You can access the related objects both in queries and as a manager attribute on your models. See the examples in the documentation. In your case this would look something like:
# Now lets see which businesses the employee works for:
Business.objects.filter(businesslocations__employee=employee).distinct()
# And let's see each business's employees:
Employee.objects.filter(business_locations__business=business).distinct()
Good evening,
I am working on some little website for fun and want users to be able to add items to their accounts. What I am struggling with is coming up with a proper solution how to implement this properly.
I thought about adding the User Object itself to the item's model via ForeignKey but wouldn't it be necessary to filter through all entries in the end to find the elements attached to x user? While this would work, it seems quite inefficient, especially when the database has grown to some point. What would be a better solution?
From what I understand of your use case, a User can have many items and and an Item can belong to multiple users. It this s the case, using ManyToManyField seems the way to go :
class Item(models.Model):
users = models.ManyToManyField('auth.User', related_name='items')
You can then query items from a specific user like this:
# adding an item to a user
user.items.add(my_item)
# query user items
user.items.all()
user.items.filter(name__startswith='Hello')
If you want to store additional information about the relationship, such as the date were the item was linked to the user, you have to specifiy an explicit intermediate model:
class Item(models.Model):
users = models.ManyToManyField('auth.User', through='ItemUser', related_name='users')
class ItemUser(models.Model):
"""Explicit intermediary model"""
user = models.ForeignKey('auth.User')
item = models.ForeignKey(Item)
date_added = models.DateTimeField(auto_now_add=True)
To create the binding beetween a User and an Item, just instanciate the intermediate model:
binding = ItemUser(user=user, item=item)
binding.save()
assert user in item.users.all()
You could create a model UserItems for each user with a ForeignKey pointing to the user and an item ID pointing to items. The UserItems model should store the unique item IDs of the items that belong to a user. This should scale better if items can be attached to multiple users or if items can exist that aren't attached to any user yet.