Django refering to same model instances in Abstract Model - python

I have an abstract model from which a couple of my main models are inherited. Main difficulty in this case is that I have a need in reference to the same model, like a ForeignKey to self. I have read that the ForeignKey is not possible in abstract models and GenericForeignKey can help, however I can`t really make it work.
As I understand structure should be something like following:
class BaseModel(models.Model):
versions = GenericRelation('self')
date_created = models.DateTimeField(default=datetime.datetime.now)
is_deleted = models.BooleanField(default=False)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
class FirstModel(BaseModel):
some_fields...
class AnotherModel(BaseModel):
another_fields...
But with this approach I get an error:
>>> item1 = FirstModel.objects.get(id=1)
>>> item2 = FirstModel.objects.get(id=2)
>>> item2.content_object = item1
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/home/michael/.virtualenvs/diagspecgen/lib/python3.6/site-packages/django/contrib/contenttypes/fields.py", line 245, in __set__
ct = self.get_content_type(obj=value)
File "/home/michael/.virtualenvs/diagspecgen/lib/python3.6/site-packages/django/contrib/contenttypes/fields.py", line 163, in get_content_type
return ContentType.objects.db_manager(obj._state.db).get_for_model(
AttributeError: 'ReverseGenericRelatedObjectsDescriptor' object has no attribute '_state'
Is that I am trying to reach is absolutely impossible and the only solution is to explicitly create needed fields in existing models?

I tried to replicate your problem with ForeignKey on a abstract model, but it seems to work fine with Django version 1.11.1:
class BaseModel(models.Model):
other = models.ForeignKey('self', null=True, blank=True)
class Meta:
abstract = True
class FirstModel(BaseModel):
pass
class AnotherModel(BaseModel):
pass
Using the models:
>>> fm1 = FirstModel.objects.create()
>>> fm2 = FirstModel.objects.create()
>>>
>>> fm1.other = fm2
>>> fm1.save()
The following assignment to other results in an error:
>>> am = AnotherModel.objects.create()
>>> am.other = fm1
ValueError: Cannot assign "<FirstModel: FirstModel object>": "AnotherModel.other" must be a "AnotherModel" instance.

Related

Code to display Django (Reverse) related table throws error

I'm quite new to Django and practicing Models section of Django by following its official tutorial. I also created a project of my own and try to apply similar concepts.
This is my models.py;
from django.db import models
class Experience(models. Model):
o01_position = models.CharField(max_length=50)
o02_year_in = models.DateField(null=True)
o03_year_out = models.DateField(null=True)
o04_project = models.CharField(max_length=100)
o05_company = models.CharField(max_length=50)
o06_location = models.CharField(max_length=50)
def __str__(self):
return self.o01_position}
class Prjdesc(models.Model):
o00_key = models.ForeignKey(
Experience, on_delete=models.CASCADE)
o07_p_desc = models.CharField(max_length=250)
def __str__(self):
return self.o07_p_desc
class Jobdesc(models.Model):
o00_key = models.ForeignKey(Experience, on_delete=models.CASCADE)
o08_job_desc = models.CharField(max_length=250)
def __str__(self):
return self.o08_job_desc
Now when I run below command in Python/Django shell it runs as expected with the related data.
>>> x = Experience.objects.get( pk = 2 )
>>> x
<Experience: Head Office Technical Office Manager>
Below two also work as expected:
>>> y = Prjdesc.objects.get( pk = 11 )
>>> y
<Prjdesc: Description 1>
>>> x.prjdesc_set.all()
<QuerySet [<Prjdesc: Description 1>, <Prjdesc: Description 2>]>
However this expression does not return anything although it should return its related record in Experience Class.
>>> y.experience
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Prjdesc' object has no attribute 'experience'
Could you tell me what I am missing here?
As you mentioned in one of the comments above:
Strangely it returns this; Traceback (most recent call last): File "", line 1, in AttributeError: 'Prjdesc' object has no attribute 'experience'.
You simply need to write c.o00_key not c.experience, you confused with official docs, they give their field name also as experince.
Generally, ForeignKey is created using model name in smallcase while defining field, and the related_name sets to model name as prefix and _set as suffix by default, so it will be prjdesc_set in your case or you can override it by using ForeignKey.related_name in the field.
With your current models use this:
>>> x = Experience.objects.get(pk=2)
>>> x
<Experience: Head Office Technical Office Manager>
>>> c = x.prjdesc_set.create(o07_p_desc='Description 5')
>>> c
<Prjdesc: Description 5>
>>> c.o00_key
>>> c
<Experience: Head Office Technical Office manager>
Note: Also it's better to use f stings in the __str__() method, so in your models.py:
class Experience(models.Model):
...
...
def __str__(self):
return f"{self.o01_position}"
class Prjdesc(models.Model):
...
...
def __str__(self):
return f"{self.o07_p_desc}"
class Jobdesc(models.Model):
...
...
def __str__(self):
return f"{self.o08_job_desc}"
If you pay attention:
c = q.choice_set.create(choice_text='Just hacking again', votes=0)
there is a call through _set. But not at all:
q = Question.objects.get(pk=1)
followed by:
q.choice# which will throw the same error
By using the primary model, you can get the data associated with it from the secondary model. To do this, a special property (object) with the name secondary model_set is created in the primary model by default. In your case, for example:
x = Experience.objects.get(pk=1)
x.prjdesc_set.all()
That is, we get all the values of the secondary model with pk=1 of the primary one (one-to-many access).
If you need to get the value from the primary model from the secondary model, then as already mentioned:
Prjdesc.objects.get(id=1).o00_key.o01_position
In this case, get is used, that is, the value should be one, if more is expected, then filter should be applied.

how can i access to the intermediary table fields through the parent table in django?

this is my tables:
class MyUser(models.Model):
pass
class Product(models.Model):
name = models.CharField()
class Order(models.Model):
customer = models.ForeignKey(MyUser, on_delete=models.CASCADE)
products = models.ManyToManyField(Product , through='MidCartProduct')
class MidCartProduct(models.Model):
class Meta :
unique_together = (('order_id' , 'product_id'))
order_id = models.ForeignKey(Order , on_delete=models.CASCADE)
product_id = models.ForeignKey(Product , on_delete=models.CASCADE)
quantity = models.PositiveSmallIntegerField()
and so how can i access to the intermediary table quantity field through the Order table?
is that possible?
i tried to do this but not working :
>>> a = Order.objects.first().products.first()
>>> a.quantity
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Order' object has no attribute 'quantity'
Sorry for my bad English
You iterate over the midcartproduct_set instead:
myorder = Order.objects.first()
for midcartproduct in myorder.midcartproduct_set.all():
print(midcartproduct.quantity, midcartproduct.product_id)
Since you likely need all Products, you better add a .select_related(…) [Django-doc] for that:
myorder = Order.objects.first()
for midcartproduct in myorder.midcartproduct_set.select_related('product_id'):
print(midcartproduct.quantity, midcartproduct.product_id)
Note: Normally one does not add a suffix _id to a ForeignKey field, since Django
will automatically add a "twin" field with an _id suffix. Therefore it should
be product, instead of product_id.

Django - Select related or join on two different base models

I have two base models SiteData and Showoroom Service that have model structure as per the below.
I need the SiteData info but I also would like to get link_type from the showroomservice model if there is a matching ID.
ive tried a few things thus far, none are getting what I need, what is the best way to achieve this?
Thanks
select related
>>> nd = ShowroomService.objects.select_related('site').all()
>>> nd
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py", line 229, in __repr__
return '<%s %r>' % (self.__class__.__name__, data)
File "/usr/local/lib/python3.6/site-packages/django/db/models/base.py", line 590, in __repr__
u = six.text_type(self)
TypeError: __str__ returned non-string (type SiteData)
Combining :
>>> complete_data = site_data | monitoring_data
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python3.6/site-packages/django/db/models/query.py", line 310, in __or__
combined.query.combine(other.query, sql.OR)
File "/usr/local/lib/python3.6/site-packages/django/db/models/sql/query.py", line 529, in combine
"Cannot combine queries on two different base models."
AssertionError: Cannot combine queries on two different base models.
chaining
>>> final_data = chain(monitoring_data, site_data)
>>> for i in final_data:
... '{} {}'.format(i.location,i.link_Type)
...
Traceback (most recent call last):
File "<console>", line 2, in <module>
AttributeError: 'ShowroomService' object has no attribute 'location'
sites.models.py
class SiteData(models.Model):
location = models.CharField(max_length=50)
site_type = models.ForeignKey(SiteTypes, verbose_name="Site Type", \
on_delete=models.PROTECT)
subnet = models.GenericIPAddressField(protocol='IPv4')
routed_subnet = models.GenericIPAddressField(protocol='IPv4', \
verbose_name="Routed Link Subnet", blank=True, null=True)
bgp_as = models.CharField(max_length=6, verbose_name="BGP AS Number")
opening_date = models.DateField(verbose_name="Showroom opening date")
last_hw_refresh_date = models.DateField(verbose_name="Date of latest hardware refresh", \
blank=True, null=True)
is_live = models.BooleanField(default=False, verbose_name="Is this a live site?")
tel = models.CharField(max_length=20, blank=True, null=True)
address = models.CharField(max_length=255, blank=True, null=True)
town = models.CharField(max_length=255, blank=True, null=True)
...
class Meta:
verbose_name = "Site Data"
verbose_name_plural = "Site Data"
ordering = ('location',)
permissions = (
("can_view", "Can View"),
("can_view_mgmt", "Can View Management"),
)
def __str__(self):
return self.location
monitoring.models.py
from sites.models import SiteData
class ShowroomService(models.Model):
site = models.ForeignKey(SiteData, verbose_name="Site", \
on_delete=models.PROTECT)
link_type = models.CharField(max_length=200, blank=True, null=True)
preference = models.CharField(max_length=200, blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True, blank=True, null=True)
dashboard = models.BooleanField(default=True, verbose_name="display on monitoring dashboard?")
class Meta:
verbose_name = "Showroom Service Data"
verbose_name_plural = "Showroom Service Data"
def __str__(self):
return self.site
You can pull all related objects using django's "related manager". Documentation is available here: https://docs.djangoproject.com/en/2.0/ref/models/relations/#related-objects-reference
By calling showroom_service_set on a SiteData model, you can get the set of child records for each SiteData.
Just to explain why the listed attempts failed as well:
your __str__ methods on your models need to return strings. If they don't, you'll get that exception.
The pipe operator | is used for ORing together queries in django. That chunk of code is attempting to combine a query for 2 different types of models.
With the chain attempt, you've created a list containing two different types of models. One of which doesn't have a location attribute.
Here's a chunk of code to get the link type for all ShowroomService models attached to a SiteData model:
for site_data in SiteData.objects.all():
for showroom in site_data.showroom_service_set.all():
print showroom.link_type
I'm not sure how django handles camel-casing with related objects, so showroom_service_set is my best guess. You might have to do a little leg work on this to figure out what the actual set is called.
EDIT: There's something called prefetch_related. Here's a SO answer about it; I think it'll get you what you're looking for: https://stackoverflow.com/a/13096423/769971

Django Many2many field Attribute Error

I'm getting an unexpected error when trying to add data to a many2many field in a django model. I'm not sure why I'm not getting the related data (payment_method)
For the models below
class PaymentMethod(models.Model):
MONTHLY_DIRECT_DEBIT = 'MDD'
CASH_OR_CHEQUE = 'CAC'
QUARTERLY_DIRECT_DEBIT = 'QDD'
PAYMENT_CHOICES = (
(MONTHLY_DIRECT_DEBIT, 'Monthly Direct Debit'),
(CASH_OR_CHEQUE, 'Cash or Cheque'),
(QUARTERLY_DIRECT_DEBIT, 'Quarterly Direct Debit'),
)
unique_id = models.CharField(max_length=3, choices=PAYMENT_CHOICES)
name = models.CharField(max_length=255, unique=True)
class Tariff(models.Model):
name = models.CharField(max_length=255)
supplier = models.ForeignKey(Supplier)
payment_method = models.ManyToManyField(PaymentMethod)
region = models.ManyToManyField(Region)
class Meta:
unique_together = ('name', 'supplier')
class Supplier(models.Model):
unique_id = models.IntegerField(unique=True)
name = models.CharField(max_length=255, unique=True)
I am trying to add the payment method data to the tariff via:
supplier = Supplier.objects.get(unique_id=region.default_supplier.unique_id)
payment_method_instance = PaymentMethod.objects.get(unique_id=payment_method['id'])
tariff, created = Tariff.objects.get_or_create(name=tariff, supplier=supplier)
if created:
sys.stdout.write('Tariff {} not found for Supplier: {}\n'.format(tariff, supplier))
tariff.payment_method.add(payment_method_instance)
when I try to access the tariff's payment method via the below I get the below error:
>>> tariff1.payment_method
Out[4]: <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager at 0x11326c710>
>>> tariff.payment_method.unique_id
Traceback (most recent call last):
File "", line 3066, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-5-a0370f309c95>", line 1, in <module>
tariff.payment_method.unique_id
AttributeError: 'ManyRelatedManager' object has no attribute 'unique_id'
The payment_method field in Tariff model, is a M2M field, so it won't return an object, it will return a Set of objects. So, you can not access to .unique_id attr because it's not an PaymentMethod object. You could get the unique_id value for all the PaymentMethod objects related with your current Tariff object:
>>> tariff1.payment_method.all().values_list('unique_id', flat=True)

csv import to manytomanyfield Django

I used below code to import csv file to django model containing manytomanyfield Release.metamodules
>>> from app.models import Metamodule,Release
>>> reldata = csv.reader(open('/root/Django-1.6.5/django/bin/dashboard/release.csv'),delimiter=',')
for row in reldata:
q = Release(number = row[0],
notes= row[1],
changes = row[2],
metamodules = Metamodule.objects.filter(name = row[3]))
try:
q.save()
except:
# if the're a problem anywhere, you wanna know about it
print "there was a problem with line"
Error:
Traceback (most recent call last):
File "<console>", line 5, in <module>
File "/usr/local/lib/python2.7/site-packages/django/db/models/base.py", line 416, in __init__
raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
TypeError: 'metamodules' is an invalid keyword argument for this function
As the field is ManyToManyField i used objects.fileter to get multiple records. But it is returning error.Please help me to fix this issue
models.py:
class Metamodule(models.Model):
name = models.CharField(max_length=50)
version = models.IntegerField(default=0)
modulename = models.ForeignKey(Module)
createdate = models.DateField(auto_now=True, null=True)
createdby = models.CharField(max_length=50)
def __unicode__(self):
return unicode(self.name)
class Release(models.Model):
number = models.IntegerField(default=0)
notes = models.CharField(max_length=50)
changes = models.CharField(max_length=50)
metamodules = models.ManyToManyField(Metamodule)
def __unicode__(self):
return unicode(self.number)
You can't create your Release object like that. You cannot create m2m relations from unsaved objects. See here
Try something like this :
for row in reldata:
q = Release(number=row[0], notes=row[1], changes=row[2])
# You have to save the object before adding the m2m relations
q.save()
metamodules = Metamodule.objects.filter(name=row[3])
for metamodule in metamodules:
q.metamodules.add(metamodule)
There is probably a better way to do the for loop but this is what you want to achieve.

Categories