My original question was actually how to add a User foreign key to Photolog type class (that uses Imagekit)
I see an answer to a similar question, but when I tried to implement it, I get global name 'system_user' is not defined
I'm not surprised by that, but I am surprised that though it's in an answer, I can't find a reference to system_user in django docs.
(It's not on docs.djangoproject.com, and Google for django+system_user returns nothing interesting.)
I have this in the class Photo in Photologue models.py
def save(self, *args, **kwargs):
if self.title_slug is None:
self.title_slug = slugify(self.title)
if 'owner' not in self.__dict__:
self.owner = system_user() # this line fails
super(Photo, self).save(*args, **kwargs)
How should I import system_user(), or what can I use here instead?
No, system_user is not a django function. You should take all code snippets as pseudo code -- he's just saying "a function that returns my object".
grep -ri "system_user" /path/to/django returns nothing, so it doesn't exist in the django source.
Check out the accepted answer in the question you linked to, he overrides the save method, passes in the user object, and manually associates the object to the user.
In your case, since you're using a model, you'd have to pass in the user object to the model save() method.
# models
def save(self, user=None, *args, **kwargs):
if self.title_slug is None:
self.title_slug = slugify(self.title)
if user:
self.owner = user
super(Photo, self).save(*args, **kwargs)
# usage in view
myobj.save(user=request.user)
Related
thanks for tanking the time to look at this query.
I'm setting an ID field within one of my Django models. This is a CharField and looks like the following:
my_id = models.CharField(primary_key=True, max_length=5,
validators=[RegexValidator(
regex=ID_REGEX,
message=ID_ERR_MSG,
code=ID_ERR_CODE
)])
I would like to add a default/blank or null option that calls a global or class function that will cycle through the existing IDs, find the first one that doesn't exist and assign it as the next user ID. However, when I add the call blank=foo() I get an error code that the function doesn't exist.
Best,
pb
Edit1: I also tried using a separate utils file and importing the function, but (unsurprisingly) I get a circular import error as I need the call the class to get the objects.
Edit2 (Reply to Eugene): Tried that, solved the circular import but I'm getting the following error:
TypeError: super(type, obj): obj must be an instance or subtype of type
Previously my override of the save function worked perfectly:
def save(self, *args, **kwargs):
self.full_clean()
super(Staff, self).save(*args, **kwargs)
The custom id function:
def get_id_default():
from .models import MyObj
for temp_id in range(10_000, 100_000):
try:
MyObj.objects.get(my_id=str(temp_id))
except ObjectDoesNotExist:
break # Id doesn't exist
return str(hive_id)
Edit 3 (Reply to PersonPr7): Unfortunately, the kwargs doesn't seem to have my id in it. Actually, after having a print the kwargs dictionary comes back empty.
Save function:
def save(self, *args, **kwargs):
print(kwargs) # --> Returns {}
if kwargs["my_id"] is None:
kwargs["my_id"] = self.get_id_default()
self.full_clean()
super(Staff, self).save(*args, **kwargs)
Where the get_id_default is a class function:
def get_id_default(self):
for temp_id in range(10_000, 100000):
try:
self.objects.get(my_id=str(temp_id))
except ObjectDoesNotExist:
break # Id doesn't exist
return str(temp_id)
Solution1:
For those who are may be struggling with this in the future:
Create a utils/script .py file (or whatever you wanna call it) and create your custom script inside.
from .models import MyModel
def my_custom_default:
# your custom code
return your_value
Inside the main.models.py file.
from django.db import models
from .my_utils import my_custom_default
class MyModel(model.Model):
my_field = models.SomeField(..., default=my_custom_default)
Solution2: Create a static function within your Model class that will create your default value.
#staticmethod
def get_my_default():
# your logic
return your_value
# NOTE: Initially I had the function use self
# to retrieve the objects (self.objects.get(...))
# However, this raised an exception: AttributeError:
# Manager isn't accessible via Sites instances
When setting up your model give your field some kind of default i.e. default=None
Additionally, you need to override the models save function like so:
def save(self, *args, **kwargs):
if self.your_field is None:
self.my_field = self.get_my_default()
self.full_clean()
super().save(*args, **kwargs)
Try overriding the Model's save method and performing the logic there:
def save(self, *args, **kwargs):
#Custom logic
super().save(*args, **kwargs)
Edit:
You don't need to use **kwargs.
You can access your whole model from the save method and loop over objects / ids.
Generally, in order to send an email when an object is created, I would override the save method:
def save(self, *args, **kwargs):
send_email(context)
return super().save(*args, **kwargs)
However, I now need the context to contain an attribute of the object that cannot be known until the object is saved, namely the url of a File object associated with the model object.
I am aware that this can be done with post_save signal, but the docs give the impression that this is best used when disparate models need access to such information. I get the impression that it's not good practice to use it in a single-model setup like this.
I've tried this:
foo = super().save(*args, **kwargs)
send_email(foo.document.url)
return foo
But foo seems to be None.
The save method doesn't return anything. But the item is self, you can use that after calling super.
super().save(*args, **kwargs)
send_email(self.document.url)
Daniel's answer is correct, but if you only want to send the email when the object is created, not if it's updated, you should also check if the instance has a pk assigned, for example:
def save(self, *args, **kwargs):
created = self.pk is None
super().save(*args, **kwargs)
if created:
send_email(context)
I have a model with a customized save() method that creates intermediate models if the conditions match:
class Person(models.Model):
integervalue = models.PositiveIntegerField(...)
some_field = models.CharField(...)
related_objects = models.ManyToManyField('OtherModel', through='IntermediaryModel')
...
def save(self, *args, **kwargs):
if self.pk is None: # if a new object is being created - then
super(Person, self).save(*args, **kwargs) # save instance first to obtain PK for later
if self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all(): # creates instances of intermediate model objects for all OtherModels
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
super(Person, self).save(*args, **kwargs) #should be called upon exiting the cycle
However, if editing an existing Person both through shell and through admin interface - if I alter integervalue of some existing Person - the changes are not saved. As if for some reason last super(...).save() is not called.
However, if I were to add else block to the outer if, like:
if self.pk is None:
...
else:
super(Person, self).save(*args, **kwargs)
the save() would work as expected for existing objects - changed integervalue is saved in database.
Am I missing something, or this the correct behavior? Is "self.pk is None" indeed a valid indicator that object is just being created in Django?
P.S. I am currently rewriting this into signals, though this behavior still puzzles me.
If your pk is None, super's save() is called twice, which I think is not you expect. Try these changes:
class Person(models.Model):
def save(self, *args, **kwargs):
is_created = True if not self.pk else False
super(Person, self).save(*args, **kwargs)
if is_created and self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all():
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
It's not such a good idea to override save() method. Django is doing a lot of stuff behind the scene to make sure that model objects are saved as they expected. If you do it in incorrectly it would yield bizarre behavior and hard to debug.
Please check django signals, it's convenient way to access your model object information and status. They provide useful parameters like instance, created and updated_fields to fit specially your need to check the object.
Thanks everyone for your answers - after careful examination I may safely conclude that I tripped over my own two feet.
After careful examination and even a trip with pdb, I found that the original code had mixed indentation - \t instead of \s{4} before the last super().save().
Can anyone see any issues with the code below? It's my save function for a model it gives them a GUID on first save. My my problem is when I save a new recipient (in the admin) it overwrites the last one added. Updates seem to work perfectly tho.
part of Models.py
class GUID():
make = hashlib.sha1(str(random.random())).hexdigest()
def save(self, *args, **kwargs):
if not self.recipientid:
self.recipientid = GUID.make
super(Recipient, self).save(*args, **kwargs)
GUID.make will be set at the time the GUID class is created, it won't re-calculated each time it's run. I don't know the rest of the context of how you're using GUID, but I'd have it be a function:
class GUID(object):
#staticmethod
def make():
return hashlib.sha1(str(random.random())).hexdigest()
...
def save(self, *args, **kwargs):
if not self.recipientid:
self.recipientid = GUID.make()
super(Recipient, self).save(*args, **kwargs)
Generally speaking, the way to do what you're trying to do is with a default lambda (in this example using a standard python uuid):
from django.db import models
from uuid import uuid4
class YourModel(models.Model):
# ...
recipientid = models.CharField(max_length=32, default=lambda: uuid4().hex)
say I've got:
class LogModel(models.Model):
message = models.CharField(max_length=512)
class Assignment(models.Model):
someperson = models.ForeignKey(SomeOtherModel)
def save(self, *args, **kwargs):
super(Assignment, self).save()
old_person = #?????
LogModel(message="%s is no longer assigned to %s"%(old_person, self).save()
LogModel(message="%s is now assigned to %s"%(self.someperson, self).save()
My goal is to save to LogModel some messages about who Assignment was assigned to. Notice that I need to know the old, presave value of this field.
I have seen code that suggests, before super().save(), retrieve the instance from the database via primary key and grab the old value from there. This could work, but is a bit messy.
In addition, I plan to eventually split this code out of the .save() method via signals - namely pre_save() and post_save(). Trying to use the above logic (Retrieve from the db in pre_save, make the log entry in post_save) seemingly fails here, as pre_save and post_save are two seperate methods. Perhaps in pre_save I can retrieve the old value and stick it on the model as an attribute?
I was wondering if there was a common idiom for this. Thanks.
A couple of months ago I found somewhere online a good way to do this...
class YourModel(models.Model):
def __init__(self, *args, **kwargs):
super(YourModel, self).__init__(*args, **kwargs)
self.original = {}
id = getattr(self, 'id', None)
for field in self._meta.fields:
if id:
self.original[field.name] = getattr(self, field.name, None)
else:
self.original[field.name] = None
Basically a copy of the model fields will get saved to self.original. You can then access it elsewhere in the model...
def save(self, *args, **kwargs):
if self.original['my_property'] != self.my_property:
# ...
It can be easily done with signals. There are, respectively a pre-save and post-save signal for every Django Model.
So I came up with this:
class LogModel(models.Model):
message = models.CharField(max_length=512)
class Assignment(models.Model):
someperson = models.ForeignKey(SomeOtherModel)
import weakref
_save_magic = weakref.WeakKeyDictionary()
#connect(pre_save, Assignment)
def Assignment_presave(sender, instance, **kwargs):
if instance.pk:
_save_magic[instance] = Assignment.objects.get(pk=instance.pk).someperson
#connect(post_save, Assignment)
def Assignment_postsave(sender, instance, **kwargs):
old = None
if instance in _save_magic:
old = _save_magic[instance]
del _save_magic[instance]
LogModel(message="%s is no longer assigned to %s"%(old, self).save()
LogModel(message="%s is now assigned to %s"%(instance.someperson, self).save()
What does StackOverflow think? Anything better? Any tips?