I have a problem with Django Rest Framework. This app I'm building is Avatar. User can update his/her own avatar, then avatar auto save with my path I defined (/users_id/photoset_id/filename.png). So I make function to save like this code:
def avatar_file_path(instance, filename):
ext = filename.split('.')[-1]
filename = '%s.%s' % (instance.id, ext)
return "users/%s/avatar/%s_%s" %(instance.user.id, instance.photoset.id, filename)
class Avatar(models.Model):
user = models.ForeignKey(User, related_name='avatar_set', null=True)
photoset = models.ForeignKey(PhotoSet, null=True, blank=True)
primary = models.BooleanField(default=True)
caption = models.TextField(blank=True, null=True)
image = models.ImageField(max_length=1024, upload_to=avatar_file_path)
is_public = models.BooleanField(_('is public'), default=True, help_text=_('Public photographs will be displayed in the default views.'))
date_uploaded = models.DateTimeField(default=datetime.datetime.now)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
# Make one primary Avatar
if self.primary:
avatars = Avatar.objects.filter(user=self.user, primary=True).exclude(id=self.id)
avatars.update(primary=False)
# Set default photoset
if self.photoset is None:
if not PhotoSet.objects.filter(user=self.user, photoset_type=3).exists():
PhotoSet.objects.create(user=self.user, photoset_type=3, title='Profile Pictures')
self.photoset = PhotoSet.objects.get(user=self.user, photoset_type=3)
if PhotoSet.objects.filter(user=self.user, photoset_type=3).exists():
self.photoset = PhotoSet.objects.get(user=self.user, photoset_type=3)
# Model Save override
if self.id is None:
saved_image = self.image
self.image = None
super(Avatar, self).save(*args, **kwargs)
self.image = saved_image
super(Avatar, self).save(force_insert, force_update, *args, **kwargs)
When I create serializers POST with Django Rest Framework:
class AvatarCreateUpdateSerializer(ModelSerializer):
class Meta:
model = Avatar
fields = [
'user',
'image',
'caption',
'is_public',
]
It goes problem:
Error Log Tracking in line: super(Avatar, self).save(force_insert, force_update, *args, **kwargs)
Why I face with this problem and how can I fix this? Thank you in advance!
You are calling two times the save() method on the base class on your Model:
Here:
super(Avatar, self).save(*args, **kwargs)
And here:
super(Avatar, self).save(force_insert, force_update, *args, **kwargs)
As the comment below says, you should be using update_or_create or get_or_create to handle case like this.
Issue Fixed: Change super(Avatar, self).save(force_insert, force_update, *args, **kwargs) into super(Avatar, self).save(*args, **kwargs)
The problem is that the save function gets passed "forced_insert=True" by the rest framework. As you are saving twice with the same data, it is trying to force an insert of the same primary key twice.
A solution is that after the first save is to reset that forced_insert by
adding
kwargs['force_insert'] = False
before the second save. That will allow Django to use the update method, hence not try and create the same primary key twice.
Related
the below form + view works fine
forms.py
class FormGlobalSettings(forms.Form):
global_font_family = forms.TypedChoiceField(required=False, label='Font Family', choices=choices_font_family, empty_value=None)
views.py
def main_view(request):
if request.method != 'POST':
form_global_settings = FormGlobalSettings()
else:
form_global_settings = FormGlobalSettings(data=request.POST)
if all([form_global_settings.is_valid()]):
cleaned_data = form_global_settings.cleaned_data
nested_data = {"fontFamily": cleaned_data3['global_font_family']}
return JsonResponse(nested_data)
return render(request, 'pbi_theme_app/index.html', {'form_global_settings': form_global_settings})
Output is:
{"fontFamily": "Arial"}
However, what I am trying to achieve is, that sometimes the POST request is blank/empty/null, as the user doesn't want to have any value in this field. The choices for the global_font_family are set up in this manner:
choices_font_family = [(None, ""), ("Arial", "Arial"), etc..]
Meaning that the if the user leaves the field empty, it results into None. This results into having it in the json as null:
{"fontFamily": null}
Now what I am trying to achieve, and because I have hardcoded "fontFamily" into the jsonresponse, I want the whole key, value pair to be gone if the user decides to have the field empty, meaning that the whole key, value pair "fontFamily: null is gone from the jsonresponse.
To summarize: I need to somehow make the key in the json dynamic, and when the POST request is empty, I want to leave the whole key, value pair from getting inputed into the json.
The intended behaviour is seen on the following webpage, when you download the theme and you didnt input anything it leaves the whole json code empty:
https://powerbi.tips/tools/report-theme-generator-v3/
Thank you :)
First of all, you don't need to include none in the choice list. You can simply declare null=True in your model.py class fields.
You can achieve the intended behavior by overwriting the intended field in your model.py class before saving the input
let assume I have the following class.
choices_font_family = [
("c1", "choice1"),
("c2", "choice2"),
("c3", "choice3"),
("c4", "choice4")
]
class Post(models.Model):
theme_type = models.CharField(null=True, max_length=255, verbose_name="some text", choices=choices_font_family )
....
....
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if theme_type is None:
theme_type = '<some font>' # overwriting none to whatever you want it to be
super().save(force_insert, force_update, *args, **kwargs)
EDIT
forms.py
from django import forms
from .models import FormGlobalSettings
class UserdataModelForm(forms.ModelForm):
class Meta:
model = FormGlobalSettings
def clean(self):
return self.clean()
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(UserdataModelForm, self).__init__(*args, **kwargs)
models.py
from django.db import models
choices_font_family = [
("c1", "choice1"),
("c2", "choice2"),
("c3", "choice3"),
("c4", "choice4")
]
class FormGlobalSettings(models.Model):
theme_type = models.CharField(null=True, max_length=255, verbose_name="some text", choices=choices_font_family )
....
....
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if theme_type is None:
theme_type = '<some font>' # overwriting none to whatever you want it to be
super().save(force_insert, force_update, *args, **kwargs)
Here is a link to Django docs on saving and overwriting save. And here is an example from Django docs on how to use forms and models.
If this solves your problem don't forget to accept this as the correct answer.
I'm looking for the most efficient way to implement this kind of mechanism in Django model.
Let's assume a situation, where there are 2 very simple models:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is incorrect
if not self.fk.value: # <--- how to do this in a proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
I know, that I can do somethink like FKModel.objects.all()/.get(), but I don't think it is the best solution (as it requires additional requests to database).
I am not sure what you try to do in your clean() method, but I assume you are trying to constrain a not null condition for the foreign key. All fields are not null constrained by default, and you have to set null=False and blank=False if you want the field to accept nulls:
class AModel(models.Model):
fk = models.ForeignKey(FKModel, null=True, blank=True)
a_value = models.CharField(max_length=150)
If you want to constrain a not null condition for a field by hand, you should do it like this:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is correct
if self.fk is None: # <--- this is the proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
For retrieving database records and its related records, you use prefetch_related, and you get your record and its related records in one single database hit:
AModel.objects.all().prefetch_related('fk')
I am trying to override the save method on a model in order to generate a unique, second auto-incrementing id.
I create my class and override the save() method, but for some reason it is erroring out with the following error:
TypeError: %d format: a number is required, not NoneType
Here's the code:
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.id
super(Person, self).save(*args, **kwargs)
Is it because I didn't pass an id parameter and it hasn't saved yet? Is there anyway to generate a value from the id?
Safest and easiest way to achieve what you want is to use a post_save signal because it is fired right after save is called, but before the transaction is committed to the database.
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=Person)
def set_person_id(sender, instance, created, **kwargs):
if created:
instance.person_id = "%07d" % instance.id
instance.save()
Yes, self.id will be Nonein some cases, and then the assignment will fail.
However you cannot just the assignment and the call to super, as suggested in the comments, because then you wouldn't be persisting the assignment to the database layer.
You need to check whether the model has an id and then proceed differently:
def save(self, *args, **kwargs):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id # Set the person_id
return super(Person, self).save(*args, **kwargs)
This issues two save operations to the database. You will want to wrap them in a transaction to make sure your database receives these two fields simultaneously.
from django.db import IntegrityError, transaction
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def create_person_id(self):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id
def save(self, *args, **kwargs):
try:
with transaction.atomic():
self.create_person_id
return super(Person, self).save(*args,**kwargs)
except IntegrityError:
raise # or deal with the error
I agree that signals might be the better option, if not, try using pk instead of id.
class Person(models.Model):
# [ . . . ]
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.pk
super(Person, self).save(*args, **kwargs)
I want to input something like(via the admin page):
text = 't(es)t'
and save them as:
'test'
on database.
And I use this Regex to modify them:
re.sub(r'(.*)\({1}(.*)\){1}(.*)', r'\1\2\3', text)
I know how to transform text from 't(es)t' to 'test' but the problem is
when i use
name = models.CharField(primary_key=True, max_length=16)
to input text(from admin). It immediately save to database cannot modify it before saving.
Finally, From a single input from admin text = 't(es)t' (CharField).
What do i want?
To use 't(es)t' as a string variable.
Save 'test' to database
Try to overide the save method in your model,
class Model(model.Model):
name = models.CharField(primary_key=True, max_length=16)
# This should touch before saving
def save(self, *args, **kwargs):
self.name = re.sub(r'(.*)\({1}(.*)\){1}(.*)', r'\1\2\3', self.name)
super(Model, self).save(*args, **kwargs)
Update:
class Model(model.Model):
name = models.CharField(primary_key=True, max_length=16)
name_org = models.CharField(max_length=16)
# This should touch before saving
def save(self, *args, **kwargs):
self.name = re.sub(r'(.*)\({1}(.*)\){1}(.*)', r'\1\2\3', self.name)
self.name_org = self.name # original "t(es)t"
super(Model, self).save(*args, **kwargs)
I need some help with an issue.
I have three models, Reference, Relation ans Circuit. Relation is an inline of the first one. Circuit and Relation are related. What I have to do is:
- I'm in Reference 1 and I have selected some Circuits inside my Relation1 to RelationN.
- When I save, I need to save Relation1 to RelationN, and other RelationFirst (created when the Reference model is saved) who must contain all the Circuits that exist in the other Relations of that Reference.
The code that I have right now, who doesn't do it, is:
class Reference(models.Model):
title = models.CharField(max_length=200, verbose_name = _('title'))
def __unicode__(self):
return u"\n %s" %(self.title)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
is_new = self.id is None
super(Reference, self).save(force_insert, force_update, *args, **kwargs)
if is_new:
Relation.objects.create(reference=self, first = True)
relation = Relation.objects.get(reference=self, first = True)
circuit = Circuit.objects.get(name = '0')
relation.circuit.add(circuit)
class Relation(models.Model):
first = models.BooleanField()
reference = models.ForeignKey(Reference)
circuit = models.ManyToManyField('Circuit', verbose_name = _('Circuits'), null=True, blank=True, related_name = 'relation_circuit')
def __unicode__(self):
return u"%s" %(self.reference)
def save(self, force_insert=False, force_update=False, *args, **kwargs):
relation1 = Relation.objects.get(reference=self.reference, first = True)
super(Relation, self).save(force_insert, force_update, *args, **kwargs)
for circ in self.circuits:
circuit = Circuit.objects.get(pk = circ)
relation1.circuit.add(circuit)
Any help? Because I can't iterate the ManyToManyRelatedField, and I don't know how to do it. Thank you very much!
You should do it that way:
for circ in self.circuit.all():