Data models dependencies, can I simplify these objects? - python

Essentially I have 6 object models, 3 for my regular objects and 3 for the dependencies of those objects. Individually, these objects can be dependent on one or multiple instances of each of the 3 regular objects.
My Question:
Is this best practice? I essentially want to be able to add a new dependency to a regular object when needed. For example:
a = A.objects.get(id=1)
adependency = ADependencies.objects.get(dependentTo=a)
Then, I'd have an object with all of the dependencies for a.
Alternatively, I did think of a way to merge the 3 dependency objects into one; however, I'm unsure whether it's good practice.
class Dependencies(models.Model):
id = models.CharField(max_length=16)
dependentTo = CharField(max_length=16)
a = models.ManyToManyField(A)
b = models.ManyToManyField(B)
c = models.ManyToManyField(C)
In this scenario, I don't use a ForeignKey to map the dependentTo object. Instead, I would use the object id to pull the object - allowing me to be class agnostic; however, this would require unique ids throughout the 3 regular objects.
a = A.objects.get(id=1)
adependency = ADependencies.objects.get(dependentTo=a.id)
One more idea!
Is it possibly to still use ForeignKey's, but pass in a string with the class name instead?
class Dependencies(models.Model):
id = models.CharField(max_length=16)
type = models.CharField(max_length=16)
dependentTo = ForeignKey(type)
a = models.ManyToManyField(A)
b = models.ManyToManyField(B)
c = models.ManyToManyField(C)
Object Models:
class A(models.Model):
id = models.CharField(max_length=16)
title = models.CharField(max_length=32)
summary = models.CharField(max_length=256)
class B(models.Model):
id = models.CharField(max_length=16)
title = models.CharField(max_length=32)
summary = models.CharField(max_length=256)
a = models.ForeignKey(A)
class C(models.Model):
id = models.CharField(max_length=16)
title = models.CharField(max_length=32)
summary = models.CharField(max_length=256)
b = models.ForeignKey(B)
class ADependencies(models.Model):
id = models.CharField(max_length=16)
dependentTo = models.ForeignKey(A)
a = models.ManyToManyField(A)
b = models.ManyToManyField(B)
c = models.ManyToManyField(C)
class BDependencies(models.Model):
id = models.CharField(max_length=16)
dependentTo = models.ForeignKey(B)
a = models.ManyToManyField(A)
b = models.ManyToManyField(B)
c = models.ManyToManyField(C)
class CDependencies(models.Model):
id = models.CharField(max_length=16)
dependentTo = models.ForeignKey(B)
a = models.ManyToManyField(A)
b = models.ManyToManyField(B)
c = models.ManyToManyField(C)
Thanks!

This can be done a lot simpler. Define a Dependency model instead of a Dependencies model. You will need the contenttypes django contribution:
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Dependency(models.Model):
dependent_contenttype = models.ForeignKey(ContentType)
dependent_id = models.PositiveIntegerField()
dependent = GenericForeignKey('dependent_contenttype', 'dependent_id')
prerequisite_contenttype = models.ForeignKey(ContentType)
prerequisite_id = models.PositiveIntegerField()
prerequisite = GenericForeignKey('prerequisite_contenttype',
'prerequisite_id')
Then you can make dependencies a reverse generic relation on models A, B and C if you like:
class A:
# ...
dependencies = GenericRelation(
Dependency,
content_type_field='dependent_contenttype',
object_id_field='dependent_id')

I think inheritance may simplify your data structures a lot.
Let's leave model A as-is:
class A(models.Model):
id = models.CharField(max_length=16)
title = models.CharField(max_length=32)
summary = models.CharField(max_length=256)
Your classes B and C has those fields of A plus one additional, so we may rewrite that as
class B(A):
a = models.ForeignKey(A)
class C(A):
b = models.ForeignKey(B)
Now as we have one base class, we only need one dependency class:
class ADependencies(models.Model):
id = models.CharField(max_length=16)
dependentTo = models.ForeignKey(A)
dependents = models.ManyToManyField(A)
Now you can set any of A, B and C objects to dependentTo and dependents. If you only need the main object from dependency, the object of type A will have either attribute b, attribute c or none of them. You may also query on those attributes:
ADependencies.objects.filter(dependentTo__b__isnull=False)
This structure is more scalable and more easily maintainable because if you need to add one more model, you only need to write the unique code for it and don't have to deal with dependency classes.
One more way to simplify your models is to just have one model:
class A(models.Model):
id = models.CharField(max_length=16)
title = models.CharField(max_length=32)
summary = models.CharField(max_length=256)
a = models.ForeignKey(A, null=True)
This way you only have model A and you may leave field a empty (indication that it is just a simple A instance) or set the value of a (it will mean the object of type B or C). Then your dependency class is the same as in previous example, but you don't need to deal with those backwards relations to test for true object type.
If you really need to disinguish between B and C objects you may write your A class like this:
class A(models.Model):
A = 0
B = 1
C = 2
TYPE_CHOICES = (
(A, "A"),
(B, "B"),
(C, "C")
)
id = models.CharField(max_length=16)
title = models.CharField(max_length=32)
summary = models.CharField(max_length=256)
a = models.ForeignKey(A, null=True)
obj_type = models.IntegerField(choices=TYPE_CHOICES)
This way you have just one model class and one dependency class and can tell what type the object is by checking obj_type. Also you should implement some check to prevent cases where a is not null and obj_type is A and similar.
Let me know if this solution is what you need.

Related

Django model filter elements

I have four models as follows:
class modelA(models.Model):
name = models.CharField(...)
class modelB(models.Model):
date = models.DateTimeField(...)
A = models.ForeignKey(modelA, ...)
class modelC(models.Model):
email = models.CharField(...)
B = models.ForeignKey(modelB, ...)
class modelD(models.Model):
uid = models.CharField(...)
C = models.ForeignKey(modelC)
Given modelA element id, I have to filter modelD elements based on that id. But I am not sure about how to do that.
I appreciate any ideas!
modalD.objects.filter(C__B__A__name ='name')
when you use double underscore you filter the related Inheritance modal

How can I mock django model object?

For example, I have a lot of interrelated tables in my project
class A(models.Model):
name = models.models.CharField(max_length=16)
class B(models.Model):
name = models.models.CharField(max_length=16)
a = models.ForeignKey(A, on_delete=models.CASCADE)
class C(models.Model):
name = models.models.CharField(max_length=16)
b = models.ForeignKey(B, on_delete=models.CASCADE)
and so on.
I need to test model C and have no interest in A and B models. Is there any chance to mock model B that can be used when creating model C objects?
I mean I'd like to create few objects without building a massive base just to test one tiny model.
You can use bakery
from model_bakery import baker
AmodelInstance = baker.make(A)
If it is only for some tests and you only need a few instances you could do something like this:
model_a_instance = A(name='somename')
model_b_instance = B(name='somename2', a=model_a_instance)
model_c_instance = C(name='somename3', b=model_b_instance)
Nonetheless, if you need to save model_c in the DB make sure to save model_a and model_b also.
If you are going to need these models more extensively I would recommend creating a factory class to populate your models.
Hopefully this helped.
After couple of days of research I don't have an answer to my question, but I think I found a tool that takes care of 'preparing the base' before actual testing or greatly simplifies it.
I have about 1000 lines of test code in my project and I decided to switch to pytest and rewrite tests almost from scratch, so, I'm actually doing what I hoped to avoid, but it's acceptable for me in this case.
Instead of creating queryset objects via django ORM I utilize Factory Boy to define factories that resolve database relations in the way I want.
For the example case I would implement something like that:
import factory
class AFactory(factory.django.DjangoModelFactory):
class Meta:
model = A
class BFactory(factory.django.DjangoModelFactory):
class Meta:
model = B
a = factory.SubFactory(AFactory)
class CFactory(factory.django.DjangoModelFactory):
class Meta:
model = C
b = factory.SubFactory(BFactory)
So, to test C model you're just creating C model object via CFactory.create(), and all 'chain' stuff is handled using SubFactories.
However, example models have no any constraints, unlike real database tables. Here's an example for more descriptive answer:
class Country(models.Model):
name = models.CharField(choices=(('BY', 'Belarus'), ('UA', 'Ukraine')),
max_length=64, unique=True)
class Employee(models.Model):
name = models.CharField(max_length=64)
email = models.EmailField(max_length=100, unique=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
class Supervisor(models.Model):
name = models.CharField(max_length=64)
email = models.EmailField(max_length=100, unique=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
If I would use same approach as with A B C example and executed something like SupervisorFactory.create() at least 2 times I violated Country.name and Employee.email constraints. 2 SupervisorFactory calls would try to create 4 countries, but it's possibly to create only 3 - 'BY', 'UA', and empty string that is factory boy's default. Thus, you should specify the rules factories generate data by:
class CountryFactory(factory.django.DjangoModelFactory):
class Meta:
model = Country
django_get_or_create = ('name',)
name = factory.Iterator('BY', 'UA')
class EmployeeFactory(factory.django.DjangoModelFactory):
class Meta:
model = Employee
email = factory.Sequence(lambda nums: 'employee.%04d#employee.io' % nums)
country = factory.SubFactory(CountryFactory)
class Supervisor(factory.django.DjangoModelFactory):
class Meta:
model = Supervisor
email = factory.Sequence(lambda nums: 'supervisor.%04d#supervisor.io' % nums)
country = factory.SubFactory(CountryFactory)
employee = factory.SubFactory(EmployeeFactory)
Hope this will save your time.

Django extract queryset from ManyToMany with through field

Say we have those models:
class A(models.Model):
field = models.ManyToManyField(B, through="C")
class B(models.Model):
value = models.CharField()
class C(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
order = models.IntegerField()
Is there an option to extract queryset of B's, but taking into consideration order field?
Doing a a.c_set.all() returns queryset for C class (but it's ordered).
Doing a a.fields.all() works, but the queryset is unordered.
I need a queryset for initializing the formset.
I hope it's understandable - it's quite late and i can't think clearly already... I'll try to clear it out if anyone has any questions.
Thanks in advance
If you put a an ordering on model C, all queryset on C would obey that order:
class C(models.Model):
class Meta:
ordering = ('order', )
Now if you want B objects related to A, you could sort the Bs based on C's ordering:
b_results = a.fields.order_by('c')
Or if the order_by('c') is not clear enough, you could change your model to be:
class C(models.Model):
a = models.ForeignKey(A, related_name='a_relationship')
b = models.ForeignKey(B)
order = models.IntegerField()
class Meta:
ordering = ('order', )
Then you could do:
b_results = a.fields.order_by('a_relationship')
Use the C model reverse relations to do the order, e.g.
a.fields.order_by(c__order)

Django get base model from parent model

Is there any way to call parent model from base model. my model.py looks like:
models.py
Class A(models.Model):
title = models.CharField(max_length=350)
description = models.TextField()
Class B(models.Model):
reftitle = models.CharField(max_length=100)
inventory = models.IntegerField()
Class C(models.Model):
a = models.ForeignKey('A')
b = models.ForeignKey('B')
notes = models.TextField()
for some reasons I can only retrieve data from Class A is there any possible way I can retrieve Class C by reference to Class A.
To get all C from given A:
a = A()
list_of_c = a.c_set.all()
and get A from given C:
c = C()
a = c.a
The reference has an own section about Many-to-one-relationships.

Django: correct order of these models

I have these models in my app models.py:
class A(models.Model):
#some details
pass
class B(models.Model):
a = models.ForeignKey(A, null=True, blank=True)
c = models.ForeignKey(C, null=True, blank=True)
class C(models.Model):
pass
def method(self):
b_list = B.objects.filter(c=self)
a_list = []
for b in b_list:
a_list.append(b.a)
return a_list
this gives me an error when i launch the webserver because in B it declares that C is not defined.
then if i put these models in order A C B django tells me that B is not defined in C's method().
How can i resolve this "not defined" issue in this situation? it seems circular!
You can always use a string in such cases:
class A(models.Model):
#some details
pass
class B(models.Model):
a = models.ForeignKey("A", null=True, blank=True) # note the quotes
c = models.ForeignKey("C", null=True, blank=True) # note the quotes
class C(models.Model):
pass
If this was a more "extreme" case and you couldn't use this trick, declaring C first, then A and B, and after that C.method (def C_method [...] C.method = C_method) would have been the way to follow.

Categories