Add attribute to custom fields in Django - 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))

Related

Trying to do a calculation on django models

i have two integer fields that i want to divide to get the value of 3rd field.
#property
def Pallets_Count(self):
return self.CASES/self.CasesPerPallet
but the result in the database always shows null .
#property
def Pallets_Count(self):
return self.CASES/self.CasesPerPallet
#property will not save anything into your model field. It works like a method. You can call in in your template like a model field mypost.Pallets_Count.
If you want to put the result into a database field, you need to override save method. But this might not be necessary. Property most likely is enough.
class MyModel(models.Model):
# your other fields
fieldname = models.FloatField()
def save(self, *args, **kwargs):
self.fieldname = self.CASES/self.CasesPerPallet
super(MyModel, self).save(*args, **kwargs)

How to save an object to be re-used in Django Rest Framework Serializer Method Fields?

The situation is as follows:
2 fields in my serializer uses the Serializer Method Field function to get their respective content.
In each of these fields, I am calling the same, exact function to generate the content.
Kinda like this:
class ProductSerializer(serializers.ModelSerializer):
field_1 = serializers.SerializerMethodField()
def get_field_1(self, obj):
content = generate_content(obj)
return content.field_1
field_2 = serializers.SerializerMethodField()
def get_field_2(self, obj):
content = generate_content(obj)
return content.field_2
As you can see, both the methods is calling the same function, with the same argument and therefore getting the exact same result. The generate_content function is very large, so I feel it would be best if I could save the content once, and use that to generate the two fields. How can I pull this off? Thanks!
you can simply put the generated content within a variable, when generate_content is called check if that variable exists or not:
class ProductSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(ProductSerializer, self).__init__(*args, **kwargs)
self.generated_content = None
def generate_content(self, obj):
if self.genretated_content:
return self.genretated_content
self.genretated_conten = 1 # gernerate here
return self.genretated_content
field_1 = serializers.SerializerMethodField()
def get_field_1(self, obj):
content = generate_content(obj)
return content.field_1
field_2 = serializers.SerializerMethodField()
def get_field_2(self, obj):
content = generate_content(obj)
return content.field_2

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

Django admin foreign key drop down field list only "test object"

I have these two classes:
class Test(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
groups = models.ManyToManyField(Group)
class TestSubjectSet(models.Model):
id = models.AutoField(primary_key=True)
test = models.ForeignKey(Test)
subject = models.ManyToManyField(Subject)
The TestSubjectSet form test list shows only string "test object".
You have to add __unicode__(self) or __str__(self) methods in your models class.
http://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#django.db.models.Model.unicode
Had the same problem. Adding
def __str__(self):
return self.user
solved the problem.
Sometimes you want to have different info returned from str function but Want to show some different info in the dropdown of admin. Then Above mentioned solution won't work.
You can do this by subclassing forms.ModelChoiceField like this.
class TestChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "Test: {}".format(obj.id)
You can then override formfield_for_foreignkey
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'test':
return TestChoiceField(queryset=Test.objects.all())
return super().formfield_for_foreignkey(db_field, request, **kwargs)