I've an Order model and others models which related with it. An user can delete any of this items and I must perform a check if the order is empty after deletion and set as active False in case true. Some basic code to ilustrate it
class Order(models.Model):
paid = models.BooleanField(default=False)
active = models.BooleanField(default=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
def empty_order():
"""
I must implement it
"""
class HomeOrder(models.Model):
...
order = models.OneToOneField(Order, related_name='primary_home')
class TourOrder(models.Model):
...
order = models.ForeignKey(Order, related_name='tours')
I have a post_delete signals that are connected with every of this Models related to Order:
post_delete.connect(delete_order_if_empty, sender=HomeOrder)
post_delete.connect(delete_order_if_empty, sender=TourOrder)
def delete_order_if_empty(sender, instance, **kwargs):
if instance.order.empty_order():
instance.order.active = False
instance.order.save()
An Order can have one Home, so if the Home exists I can do order.primary_home, if Home does not exist it will raise an AttributeError because it is an OneToOne relationship.
An Order can have many Tours, so in the empty_order method I thought to do some checks as following.
def empty_order():
home = hasattr(self, 'primary_home') # Avoid AttributeError exception
tours = self.tours.exists()
this_order_has_something = primary_home or tours
return not this_order_has_something
Now, when I delete an HomeOrder the signal is raised but the empty_method never realized that this HomeOrder does not exists any more. Example:
>>>o=Order.objects.create(...)
>>>o.primary_home # raise AttributeError
>>>h=HomeOrder.objetcs.create(order=o, ...)
>>>o.primary_home # <HomeOrder: home-xx>
>>>h.delete()
>>>o.primary_home # still <HomeOrder: home-xx> Why?
>>>o.refresh_from_db()
>>>o.primary_home # again <HomeOrder: home-xx>
Related
Is it possible to insert the session value to the foreign key?
I have 2 models
class candidate(models.Model):
fname=models.CharField("First name ",max_length=20,default="")
lname=models.CharField("Last name ",max_length=20,default="")
email=models.EmailField("Email ",max_length=254,primary_key=True)
password=models.CharField("Password ",max_length=100,default="")
def __str__(self):
return self.email #self.fname+" " +self.lname
class canDetails(models.Model):
candEmail=models.ForeignKey(candidate,on_delete=models.CASCADE)
location=models.CharField("location ",max_length=30)
role=models.CharField("role ",max_length=20)
cv=models.FileField(upload_to="media/canDetails/")
def __str__(self):
return self.candEmail
I am taking the email from above model as a session and trying to put this session value to the foreign key field of the other model, but here I am getting an error like:
Cannot assign "'cb#gmail.com'": "canDetails.candEmail" must be a "candidate" instance.
I am trying to get all the details from candidate model and candDetails model at once thats why i using pf and fk here,so is it the right way i am following...?
how can i deal with this ? any suggestions pls.?
The model canDetails needs to know to which candidate it belongs, so the Foreingkey field needs to be an candidate instance (in the database the PK value is stored). I strongly suggest, just like the Django docs also suggest, naming the field candidate and capitalizing the model names, for more clarity.
class Candidate(models.Model):
...
class CanDetails(models.Model):
candidate = models.ForeignKey(Candidate, on_delete=models.CASCADE)
...
If you only have the email, then you can use that to look up which Candidate it belongs to, fetch that instance and assign it to the CanDetails. For example:
try:
the_candidate = Candidate.objects.get(email='cb#gmail.com')
except (Candidate.DoesNotExist, Candidate.MultipleObjectsReturned):
# raise error or exit
return
det = CanDetails()
det.candidate = the_candidate
...
det.save()
I have a model (Event) that has a ForeignKey to the User model (the owner of the Event).
This User can invite other Users, using the following ManyToManyField:
invites = models.ManyToManyField(
User, related_name="invited_users",
verbose_name=_("Invited Users"), blank=True
)
This invite field generates a simple table, containing the ID, event_id and user_id.
In case the Event owner deletes his profile, I don't want the Event to be deleted, but instead to pass the ownership to the first user that was invited.
So I came up with this function:
def get_new_owner():
try:
invited_users = Event.objects.get(id=id).invites.order_by("-id").filter(is_active=True)
if invited_users.exists():
return invited_users.first()
else:
Event.objects.get(id=id).delete()
except ObjectDoesNotExist:
pass
This finds the Event instance, and returns the active invited users ordered by the Invite table ID, so I can get the first item of this queryset, which corresponds to the first user invited.
In order to run the function when a User gets deleted, I used on_delete=models.SET:
owner = models.ForeignKey(User, related_name='evemt_owner', verbose_name=_("Owner"), on_delete=models.SET(get_new_owner()))
Then I ran into some problems:
It can't access the ID of the field I'm passing
I could'n find a way to use it as a classmethod or something, so I had to put the function above the model. Obviously this meant that it could no longer access the class below it, so I tried to pass the Event model as a parameter of the function, but could not make it work.
Any ideas?
First we can define a strategy for the Owner field that will call the function with the object that has been updated. We can define such deletion, for example in the <i.app_name/deletion.py file:
# app_name/deletion.py
def SET_WITH(value):
if callable(value):
def set_with_delete(collector, field, sub_objs, using):
for obj in sub_objs:
collector.add_field_update(field, value(obj), [obj])
else:
def set_with_delete(collector, field, sub_objs, using):
collector.add_field_update(field, value, sub_objs)
set_with_delete.deconstruct = lambda: ('app_name.SET_WITH', (value,), {})
return set_with_delete
You should pass a callable to SET, not call the function, so you implement this as:
from django.conf import settings
from django.db.models import Q
from app_name.deletion import SET_WITH
def get_new_owner(event):
invited_users = event.invites.order_by(
'eventinvites__id'
).filter(~Q(pk=event.owner_id), is_active=True).first()
if invited_users is not None:
return invited_users
else:
event.delete()
class Event(models.Model):
# …
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='owned_events',
verbose_name=_('Owner'),
on_delete=models.SET_WITH(get_new_owner)
)
Here we thus will look at the invites to find a user to transfer the object to. Perhaps you need to exclude the current .owner of the event in your get_new_owner from the collection of .inivites.
We can, as #AbdulAzizBarkat says, better work with a CASCADE than explicitly delete the Event object , since that will avoid infinite recursion where an User delete triggers an Event delete that might trigger a User delete: at the moment this is not possible, but later if extra logic is implemented one might end up in such case. In that case we can work with:
from django.db.models import CASCADE
def SET_WITH(value):
if callable(value):
def set_with_delete(collector, field, sub_objs, using):
for obj in sub_objs:
val = value(obj)
if val is None:
CASCADE(collector, field, [obj], using)
else:
collector.add_field_update(field, val, [obj])
else:
def set_with_delete(collector, field, sub_objs, using):
collector.add_field_update(field, value, sub_objs)
set_with_delete.deconstruct = lambda: ('app_name.SET_WITH', (value,), {})
return set_with_delete
and rewrite the get_new_owner to:
def get_new_owner(event):
invited_users = event.invites.order_by(
'eventinvites__id'
).filter(~Q(pk=event.owner_id), is_active=True).first()
if invited_users is not None:
return invited_users
else: # strictly speaking not necessary, but explicit over implicit
return None
I have a Django model which needs to have more than 1 images and more than 1 files (numbers may vary as per requirement), for which I adjusted my Admin Panel accordingly like this
models.py
class MasterIndividualMembers(models.Model):
individualmemberId = models.CharField(primary_key=True, max_length=100, default=1)
...
...
def __str__(self):
return self.firstname + " " + self.lastname
class IndividualMemberPhotos(models.Model):
individualmemberId = models.ForeignKey(MasterIndividualMembers, default=None,on_delete=models.CASCADE)
image = models.ImageField(upload_to="individualmemberphotos/")
class IndividualMemberCatalogue(models.Model):
individualmemberId = models.ForeignKey(MasterIndividualMembers, default=None,on_delete=models.CASCADE)
files = models.FileField(upload_to="individualmembercatalogue/")
admin.py
class IndividualMemberPhotosAdmin(admin.StackedInline):
model = IndividualMemberPhotos
class IndividualMemberCatalogueAdmin(admin.StackedInline):
model = IndividualMemberCatalogue
#admin.register(MasterIndividualMembers)
class MasterIndividualMembersAdmin(admin.ModelAdmin):
inlines = [IndividualMemberPhotosAdmin,IndividualMemberCatalogueAdmin]
class Meta:
model = MasterIndividualMembers
For the views I simply make a function to provide details of all the Images, Document and that User
views.py
#csrf_exempt
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def get_individualmember(request):
if request.method == 'POST':
try:
individualmemberId = request.POST.get('individualmemberId')
result = {}
result['individualMemberDetails'] = json.loads(serializers.serialize('json', [MasterIndividualMembers.objects.get(individualmemberId=individualmemberId)]))
result['individualPhotoDetails'] = json.loads(serializers.serialize('json', IndividualMemberPhotos.objects.filter(individualmemberId__individualmemberId = individualmemberId)))
result['individualCatalogueDetails'] = json.loads(serializers.serialize('json', IndividualMemberCatalogue.objects.filter(individualmemberId__individualmemberId = individualmemberId)))
except Exception as e:
return HttpResponseServerError(e)
Problem: While fetching the details for any individual member, it throws an error get() returned more than one IndividualMemberPhotos -- it returned 2!, which is expected to have more than 1 objects.
How can I make the Restframework to provide me details of all image object together.
Instead of using get() which strictly returns a single element, use filter() which returns 0 or more elements.
As documented in https://docs.djangoproject.com/en/3.2/topics/db/queries/#retrieving-a-single-object-with-get
filter() will always give you a QuerySet, even if only a single object
matches the query - in this case, it will be a QuerySet containing a
single element.
If you know there is only one object that matches your query, you can
use the get() method on a Manager which returns the object directly:
The behavior you are experiencing is actually documented here https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.get
If get() finds more than one object, it raises a
Model.MultipleObjectsReturned exception:
I'm trying to put together a simple little app that allows users to keep track of their own pokemon. Each pokemon has 1-2 different 'types' (e.g. fire, water, etc.), so I've added in a clean function to limit the maximum number of types the user can select. However, when trying to add a pokemon I am given the following error:
AttributeError at /admin/pokollector/custompokemon/add/
'CustomPokemon' object has no attribute 'poke_types'
I'm assuming this has something to do with the poke_types variable not being properly inherited, but I have no idea why that would be.
Here is the code from my models.py file:
from django.db import models
from django.core.validators import MinValueValidator as min, MaxValueValidator as max
from django.core.exceptions import ValidationError
class PokeType(models.Model):
poke_type = models.CharField(max_length=15)
def __str__(self):
return self.poke_type
#Current generation of games for gen_added field
gen = 8
class Pokemon(models.Model):
poke_name = models.CharField(max_length=30)
poke_type = models.ManyToManyField(PokeType)
evolves_from = False
evolves_into = False
gen_added = models.PositiveIntegerField(validators=[min(1), max(gen)])
def clean(self):
#Allow max of 2 poke_types to be selected
if len(self.poke_types > 2):
raise ValidationError('A Pokemon has a maximum of two types.')
class Meta:
verbose_name_plural = 'Pokemon'
abstract = True
class CustomPokemon(Pokemon):
name = models.CharField(max_length=30)
level = models.PositiveIntegerField(blank=True, null=True)
def __str__(self):
return self.name
I think there is problem in your clean function. Try.
def clean(self):
#Allow max of 2 poke_types to be selected
if self.poke_type.count() > 2:
raise ValidationError('A Pokemon has a maximum of two types.')
It seems you mistyped.
Plus, don't use len function. When you use len, the count happens on python which is slow. Use count function so that the count occurs in database level.
Just had a few typos with the attribute name and having the comparison inside the len() call.
def clean(self):
#Allow max of 2 poke_types to be selected
if len(self.poke_type) > 2:
raise ValidationError('A Pokemon has a maximum of two types.')
I'm working on a project that manages school staff. The relationship between the two is poorly implemented in my opinion so I am trying to refactor it. However, to minimize impact, since this is not a priority I'm trying to do this without breaking the front-end, which should be refactored at a later date. The current Staff model looks like this:
class Staff(models.Model):
principal_at = models.ForeignKey(School, null=True)
teacher_at = models.ForeignKey(School, null=True)
# I'm only showing these two roles for the sake of simplicity but there are more.
I'm refactoring it to this:
class Staff(models.Model):
ROLE_CHOICES = (
"principal",
"teacher",
# ...
)
school = models.ForeignKey(School)
role = models.CharField(choices=ROLE_CHOICES)
# ...
#property
def principal_at(self):
return self.school if self.role == "principal" else None
# ...
My StaffSerializer looks like this:
class StaffSerializer(serializers.ModelSerializer):
class Meta:
model = Staff
fields = (
"principal_at",
"teacher_at",
# ...
)
# There is also some custom validation to ensure the Staff can only have
# one role.
If I just keep my it unchanged, the principal_at field becomes a ReadOnlyField and it doesn't get saved, which makes sense since my model attribute is now a property and not an actual writeable field. I changed, therefore, my serializer to this:
class StaffSerializer(serializers.ModelSerializer):
kwargs = {"required": False, "queryset": School.objects.all()}
principal_at = serializers.PrimaryKeyRelatedField(**kwargs)
teacher_at = serializers.PrimaryKeyRelatedField(**kwargs)
class Meta:
model = Staff
fields = (
"principal_at",
"teacher_at",
# ...
)
def validate(self, data):
principal_at = data.pop("principal_at", None)
teacher_at = data.pop("teacher_at", None)
# ...
if principal_at:
data.update(school_id=principal_at, role="principal")
elif teacher_at:
data.update(school_id=teacher_at, role="teacher")
# ...
return data
Now the model is saved correctly (I checked with ipdb) but in the process of building the response I get this:
TypeError: Object of type School is not JSON serializable
After this point I honestly don't remember what I've tried, but I've tried many things and nothing works. One thing I do remember trying and that blew my mind was creating a custom field and overriding to_representation:
class RelatedSchoolSerializer(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
import ipdb; ipdb.set_trace()
return value.pk
class StaffSerializer(serializers.ModelSerializer):
kwargs = {"required": False, "queryset": School.objects.all()}
principal_at = serializers.PrimaryKeyRelatedField(**kwargs)
teacher_at = serializers.PrimaryKeyRelatedField(**kwargs)
# ...
This kept failing with the same error, however, prompting me to add a breakpoint and check what the f**k is going on. Inside the debugger, to my absolute dismay, I got the following:
ipdb> value
<rest_framework.relations.PKOnlyObject object at 0x7f9563c87f90>
ipdb> value.pk
<School: School 0>
ipdb> value.pk.pk
1
ipdb>
If I return value.pk.pk instead of value.pk, everything works. But even though my test suite is passing, I'm afraid that such a weird hack may have mysterious unintended consequences down the road. What on earth is going on? What is the proper way of doing this?