Save model field value as default for another field - django

Here's a relevant code from my models.py file
class PhotoManager(models.Model):
id = models.AutoField(primary_key=True)
def model_instance_id(self):
return self.id
index = models.PositiveIntegerField(default = model_instance_id, blank=True, unique = False)
What I'm trying to do is to save model id field value into index field (to safely edit it later with AJAX calls to Django Rest Framework). As I understand, the only correct option to save model field value to another field is setting default value as a function. When I save model instance I get an error:
TypeError: model_instance_id() missing 1 required positional argument: 'self'
Anyway, I can't figure out, how to reference ID field in mentioned function.
I tried use save method, but it just does nothing
def save(self, *args, **kwargs):
if not self.index:
self.index = self.id
super().save(*args, **kwargs)

The problem is that you set the index before saving the instance, at that point, new model instance do not yet have an id. So you can perform two .save()s: one before obtaining the id, and one to save the new index.
def save(self, *args, **kwargs):
if self.id is None:
super().save(*args, **kwargs)
if self.index is None:
self.index = self.id
super().save(*args, **kwargs)
But that being said, it is still not a good idea: there are various ways to circumvent the .save(..) method, for example when updating specific fields, etc.
Therefore I advice you to make a column _index instead that is NULL-able, and then write a #property to handle the case where the index is NULL:
class PhotoManager(models.Model):
id = models.AutoField(primary_key=True)
_index = models.PositiveIntegerField(null=True, default=None, blank=True)
#property
def index(self):
if self._index is not None:
return self._index
return self.id

You should use parentheses, when calling function
index = models.PositiveIntegerField(default = model_instance_id(), blank=True, unique = False)

Related

Auto generate and save data to SlugField

Hello I have a function to auto generate data for my SlugField but i dont know how to implement a save method to execute it. Ive tried calling the function from the save method but it raises an error of 'instance not defined'. Any suggestions will help. Thanks.
def ran_str_gen(size=6, chars=string.ascii_letters + string.digits):
return ''.join(secrets.choice(chars) for s in range(size))
def slug_gen(instance, new_slug=None):
if new_slug is not None:
slug=new_slug
else:
slug = slugify(instance.title)
op = instance.__class__
qs_exists = op.objects.filter(slug=slug).exists()
if not qs_exists:
new_slug = '{slug}-{ranstr}'.format(slug=slug, ranstr=ran_str_gen())
return slug_gen(instance, new_slug=new_slug)
return slug
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
slug = models.SlugField()
def save(self, *args, **kwargs):
slug_gen()
You should pass the instance (self) to the slug_gen function, store the result in the slug field, and then make a super() call to save the model effectively, so:
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slug_gen(self)
super().save(*args, **kwargs)
Note: You can make use of django-autoslugĀ [GitHub] to automatically create a slug based on other field(s).
Note: Normally you should not change slugs when the related fields change. As is written in the article Cool URIs don't changeĀ [w3.org], URIs are not supposed to change, since these can be bookmarked. Therefore the slug should only be created when creating the object, not when you change any field on which the slug depends.
def save(self, *args, **kwargs):
self.slug=slug_gen()

django: how to know if a model is created with a m2m field not None

So basically I am trying to override the save method of a model to tell if a certain non-required field, which is a m2m field, is specified. If so, then update one of its own Boolean field to True. Currently I have something like this:
class Flag(models.Model):
name = models.CharField(max_length=300)
def __str__(self):
return self.name
class Model(models.Model):
BooleanField = models.BooleanField(default = False)
m2mField = models.ManyToManyField(Flag)
def save(self, *args, **kwargs):
super(Model, self).save(*args, **kwargs)
if Model.objects.filter(id = self.id, m2mField = None).exists():
Model.objects.filter(id = self.id).update(BooleanField = True)
And this is not working for me now. I don't really care what is in the m2m field, just trying to know if that field is specified by user when creating this instance.
TIA
Edit: I update with a more realistic definition.
You could try checking if the m2mField count is more than 0, like so:
class Model(models.Model):
BooleanField = models.BooleanField(default = False)
m2mField = models.ManyToManyField(Flag, blank=True)
def save(self, *args, **kwargs):
self.BooleanField = True if self.pk is not None and self.m2mField.count() > 0 else False
super(Model, self).save(*args, **kwargs)
It would probably a good idea to pass the blank=True argument to the m2mField.

Django: __init__() got an unexpected keyword argument 'first_name'

I am writing a class-based view that lets the employees setup their profile. Because the employee model has a few foreign key field (e.g. employer is a forieng key referencing company model), I decided not to use ModelForm and resort to good old forms so the user can enter the name of the company they work in rather than 32.
Here is my code:
class Employee_ProfileSetting(forms.Form):
first_name = forms.CharField(label = 'First Name', max_length = 30)
last_name = forms.CharField(label = 'Last Name', max_length = 30)
email = forms.EmailField()
employer = forms.CharField(max_length = 50)
cell = forms.CharField(max_length = 20)
driver_license_num = forms.CharField(max_length=20)
birth_year = forms.IntegerField()
start_date = forms.IntegerField(help_text = 'Year you started with the company')
title = forms.CharField(max_length = 30)
def __init__(self, *args, **kwargs):
if not args: # args is empty, meaning a fresh object
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
else:
# Retrieving the form's information
self.first_name = args[0].get('first_name')
self.last_name = args[0]['last_name']
self.email = args[0]['email']
self.cell = args[0]['cell']
self.driver_license_num = args[0]['driver_license_num']
self.birth_year = args[0]['birth_year']
self.start_year = args[0]['start_date']
self.title = args[0]['title']
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
The constructor would then allow me to do this in my class-based view:
# Inside class AdminSetting(View):
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
cd = form.cleaned_data
employee_profile = Employee_ProfileSetting(**cd) # Calling the constructor
employee_profile.save() # The save function is overridden
admin, created = Employee.objects.get_or_create(**cd) # If a matching employee exists, it gets that object. Otherwise, it creates it.
if created: # Object was not found, and so it was created
return HttpResponseRedirect('success.html')
When I run it, it gives me the error:
__init__() got an unexpected keyword argument 'first_name'
So, my problem is two-fold:
1) What is wrong with the code? and what does that error mean?
2) Is there a better way to let the user fill in all the fields, including the foreign key fields, in a form and save fields accordingly? e.g. the field corresponding to a foreign key is saved in its respective table first (company1.employee_set.create() and then saving the other fields. Can a ModelForm be used?
Constructor of model can't take any arguments that have no matching field inside model. So if you try to pass first_name into model that doesn't have first_name field, you will get that exception.
To do it properly, you can create multiple forms and use them. Form should ignore extra POST data and take only what it needs. Also you can pass commit=False into ModelForm.save method, so created object won't be saved. That way you can pass some additional data, for example Employee_ProfileSettings ID can be passed into Employee before saving it.
The way you define the init is very strange. It should be something like this:
def __init__(self, data, *args, **kwargs):
if not data: # data is empty, meaning a fresh object
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
else:
# Retrieving the form's information
self.first_name = data[0].get('first_name')
self.last_name = data[0]['last_name']
self.email = data[0]['email']
self.cell = data[0]['cell']
self.driver_license_num = data[0]['driver_license_num']
self.birth_year = data[0]['birth_year']
self.start_year = data[0]['start_date']
self.title = data[0]['title']
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
And second thing is how you call you class:
employee_profile = Employee_ProfileSetting(**cd)
Which expands all the clean_data list. This expands to:
employee_profile = Employee_ProfileSetting(first_name=cleaned_data[first_name], ...)
Instead you should call it like this:
employee_profile = Employee_ProfileSetting(form.cleaned_data)
So that the dictionary instance is not expanded.
Finally, I don't think I understood why you cannot use a model form...

django set default value of a model field to a self attribute

I have a model called Fattura, and I would like to set the default value of the field "printable" to a string that includes the value of the field "numero".
But I have the error that link_fattura has less arguments, but if I add default=link_fattura(self) I have an error because self is not defined.
How can I solve this issue?
class Fattura(models.Model):
def link_fattura(self, *args, **kwargs):
return u"http://127.0.0.1:8000/fatture/%s/" % (self.numero)
data = models.DateField()
numero = models.CharField("Numero", max_length=3)
fatturaProForma = models.ForeignKey(FatturaProForma)
printable = models.CharField("Fattura stampabile", max_length=200, default=link_fattura)
def __unicode__(self):
return u"%s %s" % (self.data, self.numero)
class Meta:
verbose_name_plural = "Fatture"
ordering = ['data']
You can't do this using the default argument. The best bet is to override the save method:
def save(self, *args, **kwargs):
if not self.id and not self.printable:
self.printable = self.link_fattura()
return super(Fattura, self).save(*args, **kwargs)
Sorry I read you question wrong, that's not possible without javascript because your model hasn't been saved at that stage yet.
You could forecast the url by doing something like:
def link_facttura(self):
if not self.id:
return some_url/fattura/%d/ % Fattura.objects.latest().id+1
return u""
But it's ugly and likely to cause erros once you start deleting records

How can I get a Django foreignkey using a TextInput widget to display a name, rather than an id?

I have the following (simplified for this example) Django models:
class Ingredient(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class RecipeIngredient(models.Model):
quantity = models.DecimalField(max_digits=5, decimal_places=3)
unit_of_measure = models.ForeignKey(UnitOfMeasure)
ingredient = models.ForeignKey(Ingredient)
comment = models.CharField(max_length = 40, blank=True)
def __unicode__(self):
return self.id
And I have the following form:
class RecipeIngredientForm(ModelForm):
class Meta:
model = RecipeIngredient
def __init__(self, *args, **kwargs):
super(RecipeIngredientForm, self).__init__(*args, **kwargs)
self.fields['quantity'].widget = forms.TextInput(attrs={'size':'6'})
self.fields['ingredient'].widget = forms.TextInput(attrs={'size':'30'})
self.fields['comment'].widget = forms.TextInput(attrs={'size':'38'})
When I view the form, the ingredient is displayed by its id value, not its name. How can I display the name, rather than the id?
UPDATE
A solution (more elegant ideas still welcome) is to subclass the TextInput widget and use the value to get the Ingredient name:
class IngredientInput(forms.TextInput):
def render(self, name, value, attrs=None):
new=Ingredient.objects.get(pk=value).name
value=new
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
return mark_safe(u'<input%s />' % flatatt(final_attrs))
I solved this use case by overriding a field's queryset within __init__ on the Form. A select input is still displayed but it only has one option. I had the same issue as the OP by too many options for the select.
class PaymentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PaymentForm, self).__init__(*args, **kwargs)
instance = kwargs.get("instance", None)
if instance and instance.id and instance.reconciled_by:
self.fields["reconciled_by"].queryset = User.objects.filter(
id=instance.reconciled_by.id
)
I had a similar problem and solved very similarly like this (Python 3) this also used the super class to do the rendering rather than rewriting it out again.
I have added a feature which I wanted which is to make the field read only, I left it it as I thought it might be useful for editing for what you want:
class IngredientInput(forms.TextInput):
def render(self, name, value, attrs=None):
new=Ingredient.objects.get(pk=value).name
value = self._format_value(new)
attrs['readonly'] = True
return super(IngredientInput, self).render(name, value, attrs)
IT displays the id because you said so:
class RecipeIngredient(models.Model):
def __unicode__(self):
return self.id
EDIT:
...and also, because you use a TextInput
self.fields['ingredient'].widget = forms.TextInput(attrs={'size':'30'})
I guess you need this:
https://docs.djangoproject.com/en/1.3/ref/forms/fields/#modelchoicefield