Bypassing custom Model Manager to get ManyToMany objects - python

I'm working on a maintenance project which has a model say Business with a custom Model Manager. This custom Model Manager adds some extra filter to all the queries executing on Business models. This Business model has a ManyToMany field to self named Trainers. So far so good, the issue comes in when I try to fetch all the Trainers associated with the Business without applying those filters.
The Business model is as given below:
class Business(Basetable):
#status P=publish H=inactive D=draft N=new
name = models.CharField(max_length=120)
slug = models.SlugField(max_length=150)
logo=models.OneToOneField("BusinessLogo",null=True,on_delete=models.SET_NULL)
categories = models.ManyToManyField("BusinessCategory",related_name='allcategories',null=True)
price_type = models.CharField(max_length=2,
choices=PRICE_CHOICES,
default=YEARLY, null=True, blank=True)
achievements = models.TextField(null=True, blank=True)
years_of_experience = models.FloatField(null=True, blank=True)
trainers = models.ManyToManyField("self",related_name='btrainers',null=True, blank=True, symmetrical=False)
expense=models.IntegerField(null=True,blank=True)
objects= CityManager()
def get_trainers(self):
return self.trainers.all()
get_trainers is the function which returns all the Trainers associated with the Business, however I want the results to bypass the CityManager and use the default Manager.
Any pointers will be appreciated.
Update:
using use_for_related_fields = False does not work. I found a related bug here. Is there a work around? I know that overriding the default objects is not a good practice, however this is what I have received.

In general, it's better to avoid filtering results in the default Manager:
It's a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you'd like to work with.
But if you can't change the default Manager for backwards-compatibility reasons, you can still explicitly create a plain Manager and get your results using that.
class Business(Basetable):
...
objects = CityManager() # Still the first listed, and default
plain_objects = models.Manager()
Now that you have a plain Manager, use it to explicitly access the desired objects:
def get_trainers(self):
return Business.plain_objects.filter(btrainers__id=self.id)

Related

In Django, how to keep many-to-many relations in sync?

What's the best way, in Django, to set and keep up-to-date a many-to-many field that is (for a lack of a better term) a composite of many-to-many fields from other models?
To give a concrete example, I have a Model representing a Resume.
class Resume(models.Model):
owner = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="resume",
)
description = models.TextField(default="Resume description")
skills = models.ManyToManyField(Skill, related_name="resume")
The Resume object is referenced by another model called WorkExperience:
class WorkExperience(models.Model):
...
skills = models.ManyToManyField(Skill, related_name="work")
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=False,
default=1,
related_name="work_experience",
)
resume = models.ForeignKey(
Resume,
on_delete=models.CASCADE,
null=False,
default=1,
related_name="work_experience",
)
Notice that there's a fair amount of redundancy here, with both Resume and WorkExperience pointing to the same owner etc. That said, both of these Models (Resume & WorkExperience) have a field called Skills. That reference another Model called Skills.
What I'd like is to have the Resume skills to point to the same skills as the ones in WorkExperience. Any suggestions on how to do this? I also have a Model called EducationExperience which also references Skills and has a foreign key relation to Resume. Is there any way to keep the skills in Resume be in sync with both the skills in WorkExperience and EducationExperience?
A straightforward option would be to implement a method called set_resume_skills which would, when called, add the skills they have to the list of skills that Resume has
Class WorkExperience(models.Model):
# Same model as above
def set_resume_skills(self):
self.resume.skills.add(self.skills)
The issue I have with this approach is that it only adds skills, it doesn't really keep them in sync. So if I remove a skill from WorkExperience it won't be removed from Resume. Another boundary condition would be that if multiple WorkExperience objects are referencing a skill and then I remove the skill from one Object how would I make sure that the reference in Resume is still intact? I.e., two work experience objects refer to the Skill "Javascript". I remove the Skill from one WorkExperience object. The Skill "Javascript" should still be referenced by the Resume because one WorkExperience object still has a reference to it.
Edit: The reason I want to do it like this is to reduce the amount of querying done on the front-end. If the only way to filter the skills are through the the "sub-models" (WorkExperience, EducationExperience), I'd need to do two queries in my front-end instead of one. Although now that I think about it, doing two queries isn't that bad.
By your design, it seems that Skills actually belong to owner i.e the AUTH_USER. By having its M2M relation in Resume & other models will definitely cause redundancy. Why don't you just create a M2M of Skills in User model?
The point about reducing queries, there are other ways to do this as well. By using select_related or prefetch_related
class User(models.Model):
skills = models.ManyToManyField(Skill, related_name="resume")
# ... Other fields here
class Resume(models.Model):
owner = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="resume",
)
description = models.TextField(default="Resume description")
# ... Other fields here
I was approaching the problem from the wrong end. I don't have to worry about keeping the Skills in check when I just do a reverse query on the Skill objects. Currently I'm filtering the Skill QuerySet in my view like this:
class SkillViewSet(viewsets.ModelViewSet):
serializer_class = SkillSerializer
def get_queryset(self):
queryset = Skill.objects.all()
email = self.request.query_params.get("email", None)
if email:
User = get_user_model()
user = User.objects.get(email=email)
query_work_experience = Q(work__owner=user)
query_education_experience = Q(education__owner=user)
queryset = queryset.filter(
query_work_experience | query_education_experience
)
return queryset
This removes the need to keep the Skills in sync with the Resume Model.

Design an observation pattern in Django Rest Framework

I want to implement something like the pattern which introduced in this answer. For example I have four models like this:
class Protperty(models.Model):
property_type = models.CharField(choices=TYPE_CHOICES, default='float', max_length=100)
property_name = models.CharField(max_length=100)
class FloatProperty(models.Model):
property_id = models.ForeignKey(Property, related_name='value')
value = models.FloatField(default=0.0)
class IntProperty(models.Model):
property_id = models.ForeignKey(Property, related_name='value')
value = models.IntField(default=0)
class StringProperty(models.Model):
property_id = models.ForeignKey(Property, related_name='value')
value = models.CharField(max_lenght=100, blank=True, default='')
After defining these classes, I do not know how I must implement serializer or view classes. For example for writing serializer I want to put a field value, which must be set depend of type of object currently is serialized or deserialilzed(property_type defines it).
I am new to django and rest framework too, please give me some suggestions for implementing such models and serializers.
Edit:
In general I want to construct some models that using them I able to store run time defining property with different values and able to query them further. For example I have a store and it has different goods which each one has specific property and I want to query them using a specific property.

How can I store history of ManyToManyField using django-simple-history.

How can I store history of ManyToManyField using django-simple-history. I used HistoricalRecords with attribute m2m_filds but it is throwing error: unexpected keyword argument 'm2m_fields'
I'm macro1 on GitHub, and I guess de facto maintainer of django-simple-history.
From your question it seems that you're just asking about general ManyToManyField support compared with other fields. The short answer is that we do not currently support it.
ManyToManyFields actually create an in-between model that represents the relationship between the two models you're working with.
If you want tracking on that relationship I would suggest making a 'through' model representing the relationship and passing that into the ManyToManyField constructor. You could then register that through model to have its history tracked. If you get errors like "unexpected keyword argument 'm2m_fields'" with that set up please open an issue in our tracker.
Even though django-simple-history does not allow to have history tables for many to many relations there is actually a way to achieve this.
What you can do is that you manually create the many to many table and instead of using djangos add and remove you simply create and delete the relations. If you look at it with an example we would have:
class Class(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class Student(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
classes = models.ManyToManyField(Class)
history = HistoricalRecords()
you can manually create the many to many table with:
class Class(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class Student(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class StudentClasses(models.Model):
student = models.ForeignKey(Student)
class = models.ForeignKey(Class)
history = HistoricalRecords()
if you now use:
StudentClasses.objects.create(student=student, class=class) instead of student.classes.add(class) and delete() instead of student.classes.remove(class) you will have everything tracked in a history table and the same many to many table.
As the author of django-simple-history says this isn't possible to detect change in only specific fields because
As you already know simple-history doesn't look at the values being
saved at all. It blindly saves a new historical version on every save
or delete signal.
He also says it may be possible Field Tracker do this job.

Work with instance in session before save in database

I have a system with several steps. Each step increments one single object instance.
I want to save the instance in db only in the final step, on others just update the instance I saved in the session.
My model class seems like this:
class Proposta(models.Model):
Modelo = models.ForeignKey("ModeloVersao", verbose_name="Modelo")
Pacotes = models.ManyToManyField("PacoteModelo", null=True, blank=True)
Opcionais = models.ManyToManyField("ItemModelo", null=True, blank=True)
RevestimentoInterno = models.ForeignKey("RevestimentoInternoModelo", verbose_name="Revestimento Interno")
Cor = models.ForeignKey("CorModelo")
CorSecundaria = models.ForeignKey("CorModeloSecundaria", verbose_name="Cor secundária", null=True, blank=True)
Data = models.DateTimeField(auto_now_add = True)
Status = models.CharField("Status", choices=STATUS_PROPOSTA, max_length=10)
Cliente = models.ForeignKey("Cliente")
Here's my problem:
When I try to add or retrieve m2m fields it obviously throws a ValueError with the message 'Proposta' instance needs to have a primary key value before a many-to-many relationship can be used.
I successfully got the wanted result by creating my obj instance with pk=0 but I'm sure it isn't the best way, if there is.
Do exist a way of doing that without cheating like this.
Any help would be great.
Thanks
You might find the answers to this question helpful.
Summary for quick reference:
Use ModelForms - Based on the ModelForms documentation
Save it to DB, but use a status field - I think this is less than ideal
I might add that the documentation specifically explains how to deal with M2M fields, in the section that explains The save() method.
Of those, I recommend using ModelForms. Hopefully this helps!

Putting in extra restrictions when filtering on foreignkey in django-admin

When getting members based on Unit, I only want to get the ones who are actually in that unit as of now.
I've got a model looking like this:
class Member(models.Model):
name = models.CharField(max_length=256)
unit = models.ManyToManyField(Unit, through='Membership')
class Membership(models.Model):
member = models.ForeignKey(Member)
unit = models.ForeignKey(Unit)
start = models.DateField(default=date.today)
stop = models.DateField(blank=True, null=True)
class Unit(models.Model):
name = models.CharField(max_length=256)
As you can see, members can have a "fake" membership in unit, that is only history and should not be considered in the searches and listings of the admin. They should be shown in the change-page for a single object though.
The admin looks like this:
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
class MemberAdmin(admin.ModelAdmin):
list_filter = ('unit',)
inlines = [MembershipInline,]
So how can I (if at all possible this way), when filtering on unit only get those units whose membership__stop__isnull=True?
I tried Managers, I can make them work on the model in the admin itself, but not on the filtering/searches. There is also a def queryset(self) method that is overrideable, but I can't wrap my head around how to use it to fix my problem.
Edit, how this is used: A member has only one membership in a unit, however, they could be members from before, but they are ended (with stop). So I only want to filter (and show, in the list view) those members who have an open-ended membership (like, that they are members of that unit now).
Any ideas?
So you're trying to get the members of a specific Unit, right?
unit = Unit.objects.select_related().get(id=some_id)
This will pull the unit out of the database for you, along with the Memberships and Users that belong to it. You can access and filter the users by:
for member in unit.membership__set.filter(stop__isnull=True):
print member.name
I hope this helps? I may be wrong, I haven't tested this.
One way to certainly achieve this is by adding a denormalized field for
has_open_ended_membership.
To do this just add a BooleaneField like that to the Member and make sure it's consistent.
From the django documentation this seems to be the only way without writing specialized code in the ModelAdmin object:
Set list_filter to activate filters in
the right sidebar of the change list
page of the admin. This should be a
list of field names, and each
specified field should be either a
BooleanField, CharField, DateField,
DateTimeField, IntegerField or
ForeignKey.
I'm curious about other approaches - list_filter certainly is limited.
I fixed it with putting in a denormalized field in member, with a foreign-key to the active unit. Then, to make it work and be automatically updated in the admin, I made the specialized save-function for Membership.
class Member(models.Model):
name = models.CharField(max_length=256)
unit = models.ManyToManyField(Unit, through='Membership')
unit_denorm = models.ForeignKey(Unit)
class Membership(models.Model):
member = models.ForeignKey(Member)
unit = models.ForeignKey(Unit)
start = models.DateField(default=date.today)
stop = models.DateField(blank=True, null=True)
def save(self, *args, **kwargs):
if not self.stop:
self.member.unit_denorm = self.unit
self.member.save()
super(Membership, self).save(*args, **kwargs)
class Unit(models.Model):
name = models.CharField(max_length=256)
And with list_filter = ('unit_denorm',) in the admin, it does exactly what I want.
Great! Of course, there should only be one field with stop__isnull=True. I haven't figured out how to make that restriction. but people using the system know they shouldn't do that anyway.

Categories