models.py :
class Employee(models.Model):
name = models.CharField(max_length=100)
class Department(models.Model):
name = models.CharField(max_length=100)
employee = models.ManyToManyField(Employee, null=True, blank=True)
I need to save employee ids (instead of employee object) in 'employee' ManyToManyField of 'Department' model. How to do that?
views.py:
dept = Department(name=name)
dept.save()
employee_ids = [1,2]
We can use method add (Django Docs):
Adds the specified model objects to the related object set.
dept = Department.objects.create(name=name)
dept.employee.add(*[1, 2])
Or method set(Django Docs):
Replace the set of related objects
dept.employee.set([1, 2])
Note that add(), create(), remove(), clear(), and set() all
apply database changes immediately for all types of related fields. In
other words, there is no need to call save() on either end of the
relationship.
I think this question is unclear what exactly are you trying to do ?
If you want to create a relation between department and employee on the database level django does that for you
on your current structure the relation and is like
id|department_id|user_id
--|-------------|-------
1| 3 | 2
Related
I'm building a web crawler contain links blogs etc of x website ... , I have field called number_of_crawled_Links and I want to make the value of that filed is the number of rows in another model Where Links Stored i want the process to be automatically without making request any idea how to do that
You cannot do that in fields directly, but it's good idea to do that as a method.
class ModelA(models.Model):
some_field = models.TextField()
class ModelB(models.Model):
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def get_some_field_value(self):
return self.model_a.some_field
Then ModelB can get dynamically the value of ModelA field some_field. You can do it with any type of values.
I'm trying to simply create one to many relation model of categories using Django amazing ORM.
SQL:
create table categories(
id serial primary key not null,
parent_id int
);
insert into categories values(default,default,default);
update categories set parent_id = 1 where id > 1;
select * from categories;
id | parent_id
----+-----------
2 | 1
3 | 1
1 |
(3 rows)
Django amazing orm model:
class Categories(models.Model):
id = models.AutoField(primary_key=True)
parent_id = models.ForeignKey('self')
class Meta:
managed = False
db_table = 'categories'
Django Query:
Categories.objects.get(id=1)
OUTPUT:
django.db.utils.ProgrammingError: column categories.parent_id_id does not exist
LINE 1: SELECT "categories"."id", "categories"."parent_id_id" FROM "...
^
HINT: Perhaps you meant to reference the column "categories.parent_id".
Why it uses parent_id_id column instead of parent_id and how I can force it to use parent_id?
EDIT
I just changed parent_id field to parent.
EDIT 2
tatlar answer is not in my case becouse i already have database schema.
So after digging more deeper in documentation and other questions on stackoverflow there is what i have in result. This model contains reference to parent and children categories for each row. It could be inherited for all graph alike data models (comments, categories etc).
class Categories(models.Model):
id = models.AutoField(primary_key=True)
parent = models.ForeignKey('self', on_delete=None, parent_link=True, related_name='children')
class Meta:
managed = False
db_table = 'categories'
Get all children for category 1:
from myapp.models import Categories
ch = Categories.objects.get(id=1).children print (ch)
# <QuerySet [<Categories: Categories object (2)>, <Categories: Categories object (3)>]>
Get parent for category 2:
from myapp.models import Categories
ch = Categories.objects.get(id=1).parent
print (ch)
# <Categories: Categories object (1)>
Sorry to hear that you are having trouble with Django. In time you may grow to love the Django ORM and how it abstracts all the SQL code for you :)
You need to dig a little deeper into how the ORM works -- it's not a 1:1 replacement for SQL code. Check out the Model docs.
In your specific case, you need to create a new class called Parent and reference that class (via a ForeignKey) from your Categories class (you might also like to rename your Categories class to Category -- the ORM also handles plurals).
Try the code below (where I have already renamed Categories to Category for you):
from django.db import models
class Category(models.Model):
id = models.AutoField(primary_key=True)
parent = models.ForeignKey(Parent)
# ... Extra model attributes
class Meta:
verbose_name_plural = "categories"
class Parent(models.Model):
id = models.AutoField(primary_key=True)
# ... Extra model attributes
Then add all the extra attributes you need. This will create all the database tables, and their relationships, without you ever writing any SQL. If you are used to writing SQL it is a change, but it makes sense as you work more with the ORM and understand how good it is actually architected.
Good luck!
tatlar answer is not in my case becouse i already have database schema.
So after digging more deeper in documentation and other questions on stackoverflow there is what i have in result. This model contains reference to parent and children categories for each row. It could be inherited for all graph alike data models (comments, categories etc).
class Categories(models.Model):
id = models.AutoField(primary_key=True)
parent = models.ForeignKey('self', on_delete=None, parent_link=True, related_name='children')
class Meta:
managed = False
db_table = 'categories'
Get all children for category 1:
from myapp.models import Categories
ch = Categories.objects.get(id=1).children
print (ch)
# <QuerySet [<Categories: Categories object (2)>, <Categories: Categories object (3)>]>
Get parent for category 2:
from myapp.models import Categories
ch = Categories.objects.get(id=1).parent
print (ch)
# <Categories: Categories object (1)>
class Product( models.Model ):
name = models.CharField(verbose_name="Name", max_length=255, null=True, blank=True)
the_products_inside_combo = models.ManyToManyField('self', verbose_name="Products Inside Combo", help_text="Only for Combo Products", blank=True)
However, I got this error when I tried to put the duplicate values:
From_product-to_product relationship with this From product and To
product already exists.
Screencap of the error.
Each pair (Product, Product) must be unique. This is why you get already exists error.
Behind the scenes, Django creates an intermediary join table to
represent the many-to-many relationship.
What do you want to do is to have many-to-many relationship between two models (nevermind that they are the same) with additional information stored - quantity (so you would have ProductA = 2x ProductB + ....
In order to model this relationship you will have to create intermediary model and use through option. Documentation explains it very well, so have a look:
https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
Update
Here is minimal working example:
class Product(models.Model):
name = models.CharField(verbose_name='Name', max_length=255, null=True, blank=True)
products = models.ManyToManyField('self', through='ProductGroup', symmetrical=False)
def __str__(self):
return self.name
class ProductGroup(models.Model):
parent = models.ForeignKey('Product', related_name='+')
child = models.ForeignKey('Product', related_name='+')
and admin models:
class ProductGroupInline(admin.TabularInline):
model = Product.products.through
fk_name = 'parent'
class ProductAdmin(admin.ModelAdmin):
inlines = [
ProductGroupInline,
]
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductGroup)
As you can see recursive Product-Product relation is modeled with ProductGroup (through parameter). Couple of notes:
Many-to-many fields with intermediate tables must not be symmetrical, hence symmetrical=False. Details.
Reverse accessors for ProductGroup are disabled ('+') (in general you can just rename them, however, you don't want to work with ProductGroup directly). Otherwise we would get Reverse accessor for 'ProductGroup.child' clashes with reverse accessor for 'ProductGroup.parent'..
In order to have a nice display of ManyToMany in admin we have to use inline models (ProductGroupInline). Read about them in documentation. Please note, however, fk_name field. We have to specify this because ProductGroup itself is ambiguous - both fields are foreign keys to the same model.
Be cautious with recurrency. If you would define, for example, __str__ on Product as: return self.products having ProductGroup with the same parent as the child you would loop infinitely.
As you can see in the screencap pairs can be duplicated now. Alternatively you would just add quantity field to ProductGroup and check for duplication when creating objects.
I Have a model AB that holds two foreign keys A_id and B_id.
class AB(models.Model):
A_id = models.ForeignKey('A')
B_id = models.ForeignKey('B')
field_1 = models.CharField(max_length=200, blank=True)
field_2 = models.CharField(max_length=200, blank=True)
When editing A or B, AB items are edited inlines, what I want to achieve is that when editing let's say B I want to keep the selected AB items and set the foreign key B_id to null instead of deleting them.
thanks for any hint
I wound up here because I had the same question. I think the previous answer misses the issue here -- the use case here is the user checks the "delete" checkbox on an InlineModelAdmin, not that they delete the model linked by the foreign key.
I think you can simplify the original problem, consider just that model B has a nullable foreign key to model A:
class A(models.Model):
pass
class B(models.Model):
linked_a = models.ForeignKey(A, null=True)
Then the admin lists each B linked to an A using an inline:
class BInline(TabularInline):
model = B
class AModelAdmin(ModelAdmin):
inlines = [BInline]
The question is, is there a way to make the "delete" checkbox on BInline result in B.linked_a = None rather than deleting the instance of B?
The reason this seems like a logical operation is that if you used a ManyToManyField to join these two objects, that's what would happen -- it wouldn't delete B, it would just "unlink" it.
Unfortunately, the answer as far as I can tell is that you can't do this easily. In both cases the inline is showing a database row, but while the inline for a ForeignKey is showing the related object itself, the inline for a ManyToManyField is showing a row from the join table (eg. the relationship). So in terms of database operations the "delete" action is the same, it's just that in one case you delete the related object, in the other case you just delete the relationship.
If I understand this correctly, what you want is protection against cascade deletion.
If this is the case, you need to specify what django should do on deletion of an A or B model.
From the docs:
When an object referenced by a ForeignKey is deleted, Django by default emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey. This behavior can be overridden by specifying the on_delete argument. For example, if you have a nullable ForeignKey and you want it to be set null when the referenced object is deleted:
In order to set the ForeignKey null, you can do it like this:
A_id = models.ForeignKey('A', null=True, on_delete=models.SET_NULL)
B_id = models.ForeignKey('B', null=True, on_delete=models.SET_NULL)
Good luck and hope this helps.
You can use a custom inline form set and override the delete_existing method which is available in django 1.11+.
from django.forms.models import BaseInlineFormSet
from django.db import models
from django.contrib import admin
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, null=True)
class CustomInlineFormSet(BaseInlineFormSet):
def delete_existing(self, obj, commit=True):
"""Unhook a model instead of deleting it."""
if commit:
obj.publisher = None
obj.save()
class BooktInline(admin.TabularInline):
formset = CustomInlineFormSet
This changes it so that the 'delete' action on admin inline formsets will unhook the inline model instead of deleting it.
I needed to assign one or more categories to a list of submissions, I initially used a table with two foreign keys to accomplish this until I realized Django has a many-to-many field, however following the documentation I haven't been able to duplicate what I did with original table.
My question is : Is there a benefit to using many-to-many field instead of manually creating a relationship table? If better, are there any example on submitting and retrieving many-to-many fields with Django?
From the Django docs on Many-to-Many relationships:
When you're only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
In short: If you have a simple relationship a Many-To_Many field is better (creates and manages the extra table for you). If you need multiple extra details then create your own model with foreign keys. So it really depends on the situation.
Update :- Examples as requested:
From the docs:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
You can see through this example that membership details (date_joined and invite_reason) are kept in addition to the many-to-many relationship.
However on a simplified example from the docs:
class Topping(models.Model):
ingredient = models.CharField(max_length=128)
class Pizza(models.Model):
name = models.CharField(max_length=128)
toppings = models.ManyToManyField(Topping)
There seems no need for any extra data and hence no extra model.
Update 2 :-
An example of how to remove the relationship.
In the first example i gave you have this extra model Membership you just delete the relationship and its details like a normal model.
for membership in Membership.objects.filter(person__pk=1)
membership.delete()
Viola! easy as pie.
For the second example you need to use .remove() (or .clear() to remove all):
apple = Toppings.objects.get(pk=4)
super_pizza = Pizza.objects.get(pk=12)
super_pizza.toppings.remove(apple)
super_pizza.save()
And that one is done too!