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.
Related
I would like to control some configuration settings for my project using a database model. For example:
class JuicerBaseSettings(models.Model):
max_rpm = model.IntegerField(default=10)
min_rpm = model.IntegerField(default=0)
There should only be one instance of this model:
juicer_base = JuicerBaseSettings()
juicer_base.save()
Of course, if someone accidentally creates a new instances, it's not the end of the world. I could just do JuicerBaseSettings.objects.all().first(). However, is there a way to lock it down such that it's impossible to create more than 1 instance?
I found two related questions on SO. This answer suggests using 3rd party apps like django-singletons, which doesn't seem to be actively maintained (last update to the git repo is 5 years ago). Another answer suggests using a combination of either permissions or OneToOneField. Both answers are from 2010-2011.
Given that Django has changed a lot since then, are there any standard ways to solve this problem? Or should I just use .first() and accept that there may be duplicates?
You can override save method to control number of instances:
class JuicerBaseSettings(models.Model):
def save(self, *args, **kwargs):
if not self.pk and JuicerBaseSettings.objects.exists():
# if you'll not check for self.pk
# then error will also raised in update of exists model
raise ValidationError('There is can be only one JuicerBaseSettings instance')
return super(JuicerBaseSettings, self).save(*args, **kwargs)
Either you can override save and create a class function JuicerBaseSettings.object()
class JuicerBaseSettings(models.Model):
#classmethod
def object(cls):
return cls._default_manager.all().first() # Since only one item
def save(self, *args, **kwargs):
self.pk = self.id = 1
return super().save(*args, **kwargs)
============= OR =============
Simply, Use django_solo.
https://github.com/lazybird/django-solo
Snippet Courtsy: django-solo-documentation.
# models.py
from django.db import models
from solo.models import SingletonModel
class SiteConfiguration(SingletonModel):
site_name = models.CharField(max_length=255, default='Site Name')
maintenance_mode = models.BooleanField(default=False)
def __unicode__(self):
return u"Site Configuration"
class Meta:
verbose_name = "Site Configuration"
# admin.py
from django.contrib import admin
from solo.admin import SingletonModelAdmin
from config.models import SiteConfiguration
admin.site.register(SiteConfiguration, SingletonModelAdmin)
# There is only one item in the table, you can get it this way:
from .models import SiteConfiguration
config = SiteConfiguration.objects.get()
# get_solo will create the item if it does not already exist
config = SiteConfiguration.get_solo()
If your model is used in django-admin only, you additionally can set dynamic add permission for your model:
# some imports here
from django.contrib import admin
from myapp import models
#admin.register(models.ExampleModel)
class ExampleModelAdmin(admin.ModelAdmin):
# some code...
def has_add_permission(self, request):
# check if generally has add permission
retVal = super().has_add_permission(request)
# set add permission to False, if object already exists
if retVal and models.ExampleModel.objects.exists():
retVal = False
return retVal
i am not an expert but i guess you can overwrite the model's save() method so that it will check if there has already been a instance , if so the save() method will just return , otherwise it will call the super().save()
You could use a pre_save signal
#receiver(pre_save, sender=JuicerBaseSettings)
def check_no_conflicting_juicer(sender, instance, *args, **kwargs):
# If another JuicerBaseSettings object exists a ValidationError will be raised
if JuicerBaseSettings.objects.exclude(pk=instance.pk).exists():
raise ValidationError('A JuiceBaseSettings object already exists')
I'm a bit late to the party but if you want to ensure that only one instance of an object is created, an alternative solution to modifying a models save() function would be to always specify an ID of 1 when creating an instance - that way, if an instance already exists, an integrity error will be raised.
e.g.
JuicerBaseSettings.objects.create(id=1)
instead of:
JuicerBaseSettings.objects.create()
It's not as clean of a solution as modifying the save function but it still does the trick.
I did something like this in my admin so that I won't ever go to original add_new view at all unless there's no object already present:
def add_view(self, request, form_url='', extra_context=None):
obj = MyModel.objects.all().first()
if obj:
return self.change_view(request, object_id=str(obj.id) if obj else None)
else:
return super(type(self), self).add_view(request, form_url, extra_context)
def changelist_view(self, request, extra_context=None):
return self.add_view(request)
Works only when saving from admin
The goal is removing of duplicates from list field while saving model. For example creation in migration:
def migrate_model(apps, *args):
MyModel = apps.get_model('my_app.MyModel')
m = MyModel.objects.create(
array_field=['123','123'],
)
m.array_field # ['123']
I tried to overwrite save but it doesn't work
class MyModel(models.Model):
array_field = ArrayField(models.CharField(max_length=5))
def save(self, *args, **kwargs):
if self.array_field:
self.array_field = list(set(self.array_field))
super(MyModel, self).save(*args, **kwargs)
How can I do this?
Careful, the save() method is NOT called when using create() according to django docs.
Maybe that is causing you the problems, because your overriden save method doesn't actually gets called.
I have the following code to delete a file:
from django.db import models
from django import forms
import os
class Document(models.Model):
docfile = models.FileField(upload_to='documents/%Y/%m/%d')
def __unicode__(self):
return '%s' % (self.docfile.name)
def delete(self, *args, **kwargs):
os.rmdir(os.path.join(settings.MEDIA_ROOT, self.docfile.name))
super(Document,self).delete(*args,**kwargs)
It manages to delete the objects I ask it to in my views.py but when I reupload a file of the same name it seems as though the original file still exists since I'll get "output_1.txt" instead of "output.txt".
This is the code I use to delete:
def delete_matrix():
documents = Document.objects.all()
documents.delete()
Am I not deleting the file from the database? Any help would be appreciated.
Your problem is that you are overriding the delete() method on the model but you are calling the delete method on the QuerySet returned by the default manager (Documents.object.all().delete()). These are 2 separate methods so there are 2 ways of fixing this.
1.In the delete method of the model, replace the line
os.rmdir(os.path.join(settings.MEDIA_ROOT, self.docfile.name))
by
os.remove(os.path.join(settings.MEDIA_ROOT, self.docfile.name))
AND, call the delete method for each object separately. Replace
Document.objects.all().delete()
with
documents = Document.objects.all()
for document in documents:
document.delete()
2.Replace the default manager to return a custom QuerySet which overrides the delete() method. This is explained in Overriding QuerySet.delete() in Django
Try this
document = Document.objects.get(pk=pk)
# if `save`=True, changes are saved to the db else only the file is deleted
document.docfile.delete(save=True)
here is another solution
def delete(self, *args, **kwargs):
os.remove(os.path.join(settings.MEDIA_ROOT, self.qr_code.name))
super().delete(*args, **kwargs)
You can use a much simpler code:
def delete(self, *args, **kwargs):
if self.docfile:
self.docfile.delete()
super().delete(*args, **kwargs)
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)
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)