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

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

Related

Save model field value as default for another field

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)

Save calculated value as a model field

As a follow up to my eariler question I have a new one. How can I save this calculated value as a model field. I would like to use it in my views and templates to order list by this field.
My models:
class Tournament(models.Model):
name = models.CharField(max_length=100)
date = models.DateTimeField('date')
player_num = models.IntegerField(verbose_name="")
points = models.FloatField(default=1000.00)
def get_rating(self):
return self.points / 1000.00
class TournamentStandings(models.Model):
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
player = models.ForeignKey(Player, on_delete=models.CASCADE)
player_place = models.FloatField(verbose_name=u"")
player_points = models.FloatField(verbose_name="",
blank=True) #added for testing to save the calculated value in it
#property
def get_player_points(self, obj):
return obj.tournament.player_num * obj.tournament.get_rating() -
obj.tournament.get_rating()*(obj.player_place - 1.00)
def save(self, *args, **kwargs):
self.player_points = self.get_player_points
super(TournamentStandings, self).save(*args, **kwargs)
def __float__(self):
return self.player_points
Funny as on the admin list I have a column where player_points are calculated correctly but when I add a new model instance and try to save it I get this error : 'TournamentStandings' object has no attribute 'get_player_points'. Is it bacause I am trying to do a "self" save and my calculation is (self, obj) ?? Any hints are wellcome.
Posting a working solution to my problem. No need for parentheses.
First I have fixed Tournament model, so I could save get_rating as a field:
class Tournament(models.Model):
name = models.CharField(max_length=100)
rating = models.FloatField(verbose_name="Rating", blank=True)
#property
def get_rating(self):
return (self.points) / (1000.00)
def save(self, *args, **kwargs):
self.rating = self.get_rating
super(Tournament, self).save(*args, **kwargs)
def __float__(self):
return self.rating
When I had this I tried to copy it to second model. Problem was that I could not get it to work due to related obj I was calling in my calculation. But! I have managed to assign this values to variables inside get_player_points and now all is working as intended:
class TournamentStandings(models.Model):
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
player = models.ForeignKey(Player, on_delete=models.CASCADE)
player_place = models.FloatField(verbose_name="")
player_points = models.FloatField(verbose_name="", blank=True)
#property
def get_player_points(self):
player_num = float(self.tournament.player_num)
rating = float(self.tournament.rating)
player_points = float(rating*player_num-rating*(self.player_place - 1.00))
return player_points
def save(self, *args, **kwargs):
self.player_points = self.get_player_points
super(TournamentStandings, self).save(*args, **kwargs)
def __float__(self):
return self.player_points
And this works! Any thoughts on improvements I could make are wellcome ofc.
get_player_points() is a method and requires parentheses.
def save(self, *args, **kwargs):
self.player_points = self.get_player_points()

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.

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

Add attribute to custom fields in Django

I'll be as brief as possible.
I want to able to do this
{{ video.youtube_url.video_id }}
by implementing something like the following custom field:
class YouTubeURLField(URLField):
description = _("YouTubeURL")
def _video_id(self):
return re.search('(?<=\?v\=)[\w-]+', self.value)
video_id = property(_video_id)
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
super(YouTubeURLField, self).__init__(**kwargs)
kwargs['max_length'] = kwargs.get('max_length', 200)
CharField.__init__(self, verbose_name, name, **kwargs)
self.validators.append(YouTubeURLValidator(verify_exists=verify_exists))
This:
def _video_id(self):
return re.search('(?<=\?v\=)[\w-]+', self.value)
video_id = property(_video_id)
Does not sucessfully add a "video_id" attribute to my custom YouTubeURLField.
Everything else works flawlessly.
I understand there maybe better design considerations in terms of the YouTube custom field, but I'd rather just understand, first, why this doesn't work.
Django fields are descriptors, which means that accessing them does not return the field, but rather the field value. You will need to override the Django field methods in order to return an object that has the attributes you care about, as well as a sanely-defined __unicode__() method.
Is there a reason you can't have it as a property of the model?
In order to access data from an object not directly contained within the fields I frequently implement a pattern along the lines of:
class: Sheep(models.Model):
name = models.CharField(max_length=200)
#property
def sheep_says(self):
return "Baa... my name is %s ... baa" % self.name
Which you would then access in the template with:
{{ sheep.sheep_says }}
I wanted to do it this way, because it seems it makes more sense from a design stand point. The video id is an attribute of the YouTube URL and not of the model itself.
I figured it out. I overrode the to_python function to return a YouTubeURL object.
class YouTubeURL(object):
def __init__(self, value):
self.value = value
#property
def video_id(self):
regex = re.compile(r'/v/([A-Za-z0-9\-_]+)', re.IGNORECASE)
id = regex.search(self.value)
return id.group(1)
def __unicode__(self):
return "%s" % (self.value,)
def __str__(self):
return "%s" % (self.value,)
def __len__(self):
return len(self.value)
class YouTubeURLField(URLField):
description = _("YouTubeURL")
__metaclass__ = SubfieldBase
def to_python(self, value):
return YouTubeURL(value)
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
super(YouTubeURLField, self).__init__(**kwargs)
kwargs['max_length'] = kwargs.get('max_length', 200)
CharField.__init__(self, verbose_name, name, **kwargs)
self.validators.append(YouTubeURLValidator(verify_exists=verify_exists))