Cannot assign "'somedata'": "otherdatal" must be a "" instance.` - python

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()

Related

Access model instance inside model field

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

Prefetch Without Direct Relation Django

In Django is there a way to do a prefetch without having a direct foreign key relationship?
Or specifically, to do a prefetch based off the values of two columns?
I have the models
class ProductionType(models.Model ):
name = models.CharField(max_length=100,default="New Job Progress Type")
def __str__(self):
return self.name
def label_from_instance(self, obj):
return "{0}".format(obj.name)
class ProductionRecord(models.Model):
job = models.ForeignKey(Job,null=True,on_delete=models.SET_NULL,related_name="production_records")
type = models.ForeignKey(ProductionType,null=True)
done = models.IntegerField(default=1)
and then in another app
class ShopProgress(models.Model ):
job= models.ForeignKey(Job, on_delete=models.CASCADE, related_name="shop_progress")
total=models.IntegerField(null=True)
type=models.ForeignKey(ProductionType,on_delete=models.CASCADE)
#property
def done(self):
x = ProductionRecord.objects.values_list("done").filter(job=self.job, type=self.type).aggregate(models.Sum('done'))
t =x["done__sum"]
if t == None or t== 0:
t=0
self._done=t
return t
ProductionRecord and ShopProgress both reference a Job and a Production Type.
So the relation is dependent on these two fields, not a direct foreign key.
In this particular case, i would like for the query in "ShopProgress.done()" to be prefetched when querying ShopProgress.
A similar raw query would look roughly like
select a.id, a.type_id, a.total ,sum(b.done), a.job_id from DailyProgressReport_shopprogress a
left join production_productionrecord b on b.type_id=a.type_id and b.job_id = a.job_id
group by a.job_id, a.type_id
order by a.id
I hope the question is concise. If not, let me know and i will try to provide additional clarity.

django related field exists after delete

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>

How does django one-to-one relationships map the name to the child object?

Apart from one example in the docs, I can't find any documentation on how exactly django chooses the name with which one can access the child object from the parent object. In their example, they do the following:
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.place.name
# Create a couple of Places.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
# A Restaurant can access its place.
>>> r.place
<Place: Demon Dogs the place>
# A Place can access its restaurant, if available.
>>> p1.restaurant
So in their example, they simply call p1.restaurant without explicitly defining that name. Django assumes the name starts with lowercase. What happens if the object name has more than one word, like FancyRestaurant?
Side note: I'm trying to extend the User object in this way. Might that be the problem?
If you define a custom related_name then it will use that, otherwise it will lowercase the entire model name (in your example .fancyrestaurant). See the else block in django.db.models.related code:
def get_accessor_name(self):
# This method encapsulates the logic that decides what name to give an
# accessor descriptor that retrieves related many-to-one or
# many-to-many objects. It uses the lower-cased object_name + "_set",
# but this can be overridden with the "related_name" option.
if self.field.rel.multiple:
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
return None
return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
else:
return self.field.rel.related_name or (self.opts.object_name.lower())
And here's how the OneToOneField calls it:
class OneToOneField(ForeignKey):
... snip ...
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(),
SingleRelatedObjectDescriptor(related))
The opts.object_name (referenced in the django.db.models.related.get_accessor_name) defaults to cls.__name__.
As for
Side note: I'm trying to extend the
User object in this way. Might that be
the problem?
No it won't, the User model is just a regular django model. Just watch out for related_name collisions.

How to make an auto-filled and auto-incrementing field in django admin

[Update: Changed question title to be more specific]
Sorry if I didn't make the question very well, I can't figure how to do this:
class WhatEver():
number = model.IntegerField('Just a Field', default=callablefunction)
...
Where callablefunction does this query:
from myproject.app.models import WhatEver
def callablefunction():
no = WhatEver.objects.count()
return no + 1
I want to automatically write the next number, and I don't know how to do it.
I have errors from callablefunction stating that it cannot import the model, and I think there must be an easier way to do this. There's no need even to use this, but I can't figure how to do it with the pk number.
I've googled about this and the only thing I found was to use the save() method for auto incrementing the number... but I wanted to show it in the <textfield> before saving...
What would you do?
Got it! I hope this will help everyone that has any problems making a auto-filled and auto-incrementing field in django. The solution is:
class Cliente(models.Model):
"""This is the client data model, it holds all client information. This
docstring has to be improved."""
def number():
no = Cliente.objects.count()
if no == None:
return 1
else:
return no + 1
clientcode = models.IntegerField(_('Code'), max_length=6, unique=True, \
default=number)
[... here goes the rest of your model ...]
Take in care:
The number function doesn't take any arguments (not even self)
It's written BEFORE everything in the model
This was tested on django 1.2.1
This function will automatically fill the clientcode field with the next number (i.e. If you have 132 clients, when you add the next one the field will be filled with clientcode number 133)
I know that this is absurd for most of the practical situations, since the PK number is also auto-incrementing, but there's no way to autofill or take a practical use for it inside the django admin.
[update: as I stated in my comment, there's a way to use the primary key for this, but it will not fill the field before saving]
Every Django model already has an auto-generated primary key:
id = models.AutoField(primary_key=True)
It seems you are trying to duplicate an already existing behavior, just use the object primary key.
I, too, came across this problem, my instance of it was customer.number which was relative to the customers Store. I was tempted to use something like:
# Don't do this:
class Customer(models.Model):
# store = ...
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
try:
self.number = self.store.customer_set.count() + 1
else:
self.number = 1
super(Customer, self).save(*args, **kwargs)
The above can cause several problems: Say there were 10 Customers, and I deleted customer number 6. The next customer to be added would be (seemingly) the 10th customer, which would then become a second Customer #10. (This could cause big errors in get() querysets)
What I ended up with was something like:
class Store(models.Model):
customer_number = models.IntegerField(default=1)
class Customer(models.Model):
store = models.ForeignKey(Store)
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
self.number = self.store.customer_number
self.store.number += 1
self.store.save()
super(Customer, self).save(*args, **kwargs)
PS:
You threw out several times that you wanted this field filled in "before". I imagine you wanted it filled in before saving so that you can access it. To that I would say: this method allows you to access store.customer_number to see the next number to come.
You have errors in code, that's why you can't import it:
from django.db import models
class WhatEver(models.Model):
number = models.IntegerField('Just a Field', default=0)
and Yuval A is right about auto-incrementing: you don't even need to declare such a field. Just use the pk or id, they mean the same unless there's a composite pk in the model:
> w = Whatever(number=10)
> w
<Whatever object>
> w.id
None
> w.save()
> w.id
1
[update] Well, I haven't tried a callable as a default. I think if you fix these errors, it must work.

Categories

Resources