How to remove the instance of ManyToMany field? - django

I have two models:
class Organisation(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,related_name='organisation_user',on_delete=models.CASCADE)
name = models.CharField(max_length=100,blank=True)
location = models.CharField(max_length=100,blank=True)
qualification_status = (
('Pending for verification','Pending for verification'),
('Verified','Verified'),
)
qualification = models.CharField(max_length=100,choices=qualification_status,default='Pending for verification',blank=True)
members = models.ManyToManyField(settings.AUTH_USER_MODEL,related_name='organisation_members',blank=True)
class Organisation_member(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
organisation = models.ForeignKey(Organisation,on_delete=models.CASCADE,related_name='organisation_staff')
member_name = models.ForeignKey(settings.AUTH_USER_MODEL,related_name='organisation_staff_member',on_delete=models.CASCADE,null=True,blank=True)
is_admin = models.BooleanField(default=False)
I have this signal for creation of Organisation_member model:
#receiver(post_save, sender=Organisation)
def organisation_admin(sender, instance, created, **kwargs):
for member in instance.members.all():
if Organisation_member.objects.filter(user=instance.user,organisation=Organisation.objects.filter(user=instance.user,name=instance.name).first(),member_name=member).exists():
pass
else:
Organisation_member.objects.update_or_create(User=instance.User,organisation=Organisation.objects.filter(user=instance.user,name=instance.name).first(),member_name=member,is_admin=False)
The signal indicates that when I add a member in my manytomany field it will automatically create a Organisation_member object of the selected member.
The signal works perfectly fine.
My problem is the reverse i.e. when I try to delete an object of Organisation_member it should also remove the member from the manytomany relationship of the parent model.
I have tried this:
#login_required()
def delete_members(request, pk):
user_organisation = get_object_or_404(Organisation, user=request.user)
member_to_delete = Organisation_member.objects.filter(pk=pk)
if member_to_delete.exists():
member_to_delete[0].delete()
for member in user_organisation.members.all():
user_organisation.members.remove(member=member_to_delete[0])
return redirect(reverse('userprofile:organisation_member_list'))
But it does not removes the member from the parent model..
Anyone who knows the solution please help.
Thank you

Try something like this:
#login_required()
def delete_members(request, pk):
user_organisation = get_object_or_404(Organisation, user=request.user)
member_to_delete = get_object_or_404(Organisation_member, pk=pk)
user_organisation.members.remove(member_to_delete.member_name)
member_to_delete.delete()
return redirect(reverse('userprofile:organisation_member_list'))
Refer Django offical doc

The standard way of using this kind of relationship would be to specify Organisation_member as the through model for members in Organisation. Then the database takes care of it for you without any signals.
class Organisation(models.Model):
members = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='Organisation_member')
The only problem is that you currently have two references to User in Organisation_member: member_name and user. Why? You probably only need one and should delete the other. If there is a reason for having two different users in Organisation_member, you need to specify the fields to use for the relationship:
members = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='Organisation_member',
through_fields=('organisation', 'member_name'))
That's it, Organisation_members are automatically added and removed whenever you add or remove a user to or from members.

Related

How to I automatically filter out is_deleted records in an associated table in Django?

I am using soft deletes on one of my models in Django, and I am overwriting the default manager to always return active records only, using something like:
class ActiveRecordManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveRecordManager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
objects = ActiveRecordManager()
All works well. However, when I do:
tag = Tag.objects.get(pk=100)
And then I try to get the associated photos:
photos = tag.photos.all()
Then I get photos that are deleted. I only want to return objects that are not deleted (so my regular objects list. I was reading about _base_mangers in Django, which seems to control this, but the documentation recommends against filtering objects out:
If you override the get_queryset() method and filter out any rows,
Django will return incorrect results. Don’t do that. A manager that
filters results in get_queryset() is not appropriate for use as a base
manager.
But what I am not clear about is how I am supposed to filter these results. Any thoughts?
UPDATE:
I was asked to explain how this question is different from this one:
How to use custom manager with related objects?
In this 8 year old question they mention a deprecated method. That deprecated method is superseded by the method I outline below (base_managers) which according to the documentation I should not use. If people think I should use it, can you please elaborate?
why not use custom query methods instead of overriding manager as it may produce problems for example in admin pages?
class ActiveModelQuerySet(models.QuerySet):
def not_active(self, *args, **kwargs):
return self.filter(is_deleted=True, *args, **kwargs)
def active(self, *args, **kwargs):
return self.filter(is_deleted=False, *args, **kwargs)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
you can then filter your models however you want
tag = Tag.objects.active(pk=100)
deleted_tags = Tag.objects.not_active()
photos = tag.photos.active()
also note that you need is_deleted attribute in all your models that have the soft delete functionality like Photo in your case

Best practice when updating Django model fields? Is this what manager classes are for?

I am very new to Django and I am wondering what the best practice is for updating a field. Here is my model:
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
Right now I have a separate helper file, WebsiteHelper.py, with many other functions not related to the database in it, but also this function for updating a specific field in the DB:
def __mark_needs_redone(Website):
Website.update(is_awesome=True)
Is there a cleaner place for functions such as these to live, such as:
class WebsiteManager(models.Manager)
#Execute function here
Is this how managers are supposed to be used? If not, what is the best practice here?
If the field is on the model the form is handling, You can override the save() method you access to the actual instance.
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
def save(self, commit=True):
self.instance.is_awesome = True
return super().save(commit)
Don't forget the super().save(commit) call after because the parent takes care of the saving logic
Your update() is calling the wrong way, you should call it to the model queryset, not the instance.
If You need to call Your method for every save(), check the pre_save signal, but if You don't, use Manager.
class WebsiteManager(models.Manager):
def mark_needs_redone(self, pk):
self.get(pk=pk).update(is_awesome = True)
To your model Website add the Manager:
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
objects = WebsiteManager()
And the usage is:
Website.objects.mark_needs_redone(pk=1)
That code will mark is_awesome as True for Website with pk=1

How do I use a ModelManager on a Django ManyToMany through field?

Let's say I have the following models:
class Poll(model):
title = models.CharField()
class Option(model):
title = models.CharField()
polls = models.ManyToManyField(
Poll,
through='PollOption',
null=True,
blank=True,
related_name='options'
)
class PollOptionManager(models.Manager):
use_for_related_fields = True
def get_queryset(self):
return super(PollOptionManager, self).get_queryset().filter(
is_active=True
)
class PollOption(model):
poll = ForeignKey(Poll)
option = ForeignKey(Option)
is_active = BooleanField(default=True)
objects = PollOptionManager()
When I try to query Poll.options.all() I'm still getting Option instances for which PollOption.is_active is False. How can I get my model manager to appropriately filter my ManyToMany relationship based on a flag on the through field?
The problem is that the through model's (related) manager is never actually used in your scenario. In order to utilize the custom manager, you have to explicitly use it, e.g.:
class Poll(models.Model):
#property
def active_options(self):
return Option.objects.filter(id__in=self.polloption_set.values_list('option'))
Here, polloption_set filters out inactive options as intended. This, however, makes the manager kind of pointless because you can just as well put the extra filter in the custom property.

django admin add data with fixed value in some field

class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.

Setting default value for Foreign Key attribute

What is the best way to set a default value for a foreign key field in a model? Suppose I have two models, Student and Exam with student having exam_taken as foreign key. How would I ideally set a default value for it? Here's a log of my effort
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=1)
Works, but have a hunch there's a better way.
def get_exam():
return Exam.objects.get(id=1)
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=get_exam)
But this fails with tables does not exist error while syncing.
Any help would be appreciated.
I would modify #vault's answer above slightly (this may be a new feature). It is definitely desirable to refer to the field by a natural name. However instead of overriding the Manager I would simply use the to_field param of ForeignKey:
class Country(models.Model):
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, to_field='sigla', default='IT')
As already implied in #gareth's answer, hard-coding a default id value might not always be the best idea:
If the id value does not exist in the database, you're in trouble. Even if that specific id value does exist, the corresponding object may change. In any case, when using a hard-coded id value, you'd have to resort to things like data-migrations or manual editing of existing database content.
To prevent that, you could use get_or_create() in combination with a unique field (other than id).
Here's one way to do it:
from django.db import models
class Exam(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=255)
#classmethod
def get_default_pk(cls):
exam, created = cls.objects.get_or_create(
title='default exam',
defaults=dict(description='this is not an exam'),
)
return exam.pk
class Student(models.Model):
exam_taken = models.ForeignKey(
to=Exam, on_delete=models.CASCADE, default=Exam.get_default_pk
)
Here an Exam.title field is used to get a unique object, and an Exam.description field illustrates how we can use the defaults argument (for get_or_create) to fully specify the default Exam object.
Note that we return a pk, as suggested by the docs:
For fields like ForeignKey that map to model instances, defaults should be the value of the field they reference (pk unless to_field is set) instead of model instances.
Also note that default callables are evaluated in Model.__init__() (source). So, if your default value depends on another field of the same model, or on the request context, or on the state of the client-side form, you should probably look elsewhere.
I use natural keys to adopt a more natural approach:
<app>/models.py
from django.db import models
class CountryManager(models.Manager):
"""Enable fixtures using self.sigla instead of `id`"""
def get_by_natural_key(self, sigla):
return self.get(sigla=sigla)
class Country(models.Model):
objects = CountryManager()
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, default='IT')
In my case, I wanted to set the default to any existing instance of the related model. Because it's possible that the Exam with id 1 has been deleted, I've done the following:
class Student(models.Model):
exam_taken = models.ForeignKey("Exam", blank=True)
def save(self, *args, **kwargs):
try:
self.exam_taken
except:
self.exam_taken = Exam.objects.first()
super().save(*args, **kwargs)
If exam_taken doesn't exist, django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist will be raised when a attempting to access it.
The issue with most of these approaches are that they use HARD CODED values or lambda methods inside the Model which are not supported anymore since Django Version 1.7.
In my opinion, the best approach here is to use a sentinel method which can also be used for the on_delete argument.
So, in your case, I would do
# Create or retrieve a placeholder
def get_sentinel_exam():
return Exam.objects.get_or_create(name="deleted",grade="N/A")[0]
# Create an additional method to return only the id - default expects an id and not a Model object
def get_sentinel_exam_id():
return get_sentinel_exam().id
class Exam(models.Model):
....
# Making some madeup values
name=models.CharField(max_length=200) # "English", "Chemistry",...
year=models.CharField(max_length=200) # "2012", "2022",...
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam",
on_delete=models.SET(get_sentinel_exam),
default=get_sentinel_exam_id
)
Now, when you just added the exam_taken field uses a guaranteed existing value while also, when deleting the exam, the Student themself are not deleted and have a foreign key to a deleted value.
You could use this pattern:
class Other(models.Model):
DEFAULT_PK=1
name=models.CharField(max_length=1024)
class FooModel(models.Model):
other=models.ForeignKey(Other, default=Other.DEFAULT_PK)
Of course you need to be sure that there is a row in the table of Other. You should use a datamigration to be sure it exists.
I'm looking for the solution in Django Admin, then I found this:
class YourAdmin(admin.ModelAdmin)
def get_changeform_initial_data(self, request):
return {'owner': request.user}
this also allows me to use the current user.
see django docs
the best way I know is to use lambdas
class TblSearchCase(models.Model):
weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))
so you can specify the default row..
default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')