Programatically setting a Model's Fields in Django - python

Say I have a model Food
class Food(models.Model):
description = models.TextField()
toppings = models.TextField()
scheme = models.ForeignKey(FoodScheme, models.CASCADE)
And I want to have another class, a FoodScheme which describes which of the fields must be set in a specific Food class.
class FoodScheme(models.Model):
scheme_name = models.TextField()
requires_description = models.BooleanField(default=False)
requires_toppings = models.BooleanField(default=False)
But instead of hard coding this, I want to programmatically set these fields up, so any change in Food will change the FoodScheme class too.
An example implementation (that doesn't work, for several reasons, but I think gets my point across):
class FoodScheme(models.Model):
scheme_name = models.TextField()
for f in Food.get_fields():
setattr(self, f"requires_{f.name}", models.BooleanField(default=False))
Any ideas would be greatly appreciated.

As suggested by iklinac, the easiest way to get this to work is using a JSONField. This allows the content to be dynamic.
Here's an example GenericScheme:
class GenericScheme(models.Model):
scheme_name = models.TextField(default="Unnamed Scheme")
scheme_json = models.JSONField(default=dict)
model = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.scheme_json = {}
self.update()
def update(self):
if self.model is None:
return
for f in self.model._meta.get_fields():
if f.name not in self.scheme_json:
self.scheme_json[f.name] = False

Related

Insert multiple keywords in one field

My question is how i can insert multiple keywords in one django field and show them in a template like stackoverflow tags.
Models:
class Jobs(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(blank=True, default='')
company = models.ForeignKey(Company, on_delete=models.CASCADE)
tags = ?????
Create another class and use many-to-many relationship between jobs class (tags) and new class:
class Tags(models.Model):
tag_name=models.CharField()
In jobs class
tags=models.ManyToManyField(Tags)
For show in template you can use for loop, etc.
Make it a Comma separated value.
class Jobs(models.Model):
tags = models.TextField()
def tag_list(self):
return self.tags.split(",")
def add_tag(self, tag_str):
current_tags = self.tag_list()
current_tags.append(tag_str)
current_tags = set(current_tags)
new_tag_string = ",".join(current_tags)
self.tags = new_tag_string
# you could save the model now or let caller save it outside of this method. I suggest letting caller save the model.
def remove_tag(self, tag_str):
current_tags = self.tag_list()
current_tags.remove(tag_str)
new_tag_string = ",".join(current_tags)
self.tags = new_tag_string
# you could save the model now or let caller save it outside of this method. I suggest letting caller save the model.

Django Access to Foreign Key data to set a field default value

I have two models with their respective forms. One has a Foreign Key link to the other and from, here I would like to set some fields default data.
class Lexicon(models.Model):
[...]
case_sensitive = models.BooleanField(default=True)
invariant = models.NullBooleanField(default=False)
diacritics = models.BooleanField(default=True)
[...]
class Meta:
verbose_name = "lexicon"
ordering = ["filename"]
def __str__(self):
return self.filename
class Lexeme(models.Model):
lexicon = models.ForeignKey(Lexicon, on_delete=models.CASCADE)
case_sensitive = models.BooleanField(default=True)
diacritics = models.BooleanField(default=True)
[...]
class Meta:
verbose_name = "lexeme"
I would like the Lexeme model fields "case_sensitive" and "diacritics" to default from Lexicon. I suppose the forms may be a better place to do this.
Any idea ?
As I understand, you only need to populate data from Lexicon to Lexeme model fields. You can override get_form_kwargs in your FormView as follows
def get_form_kwargs(self):
lex_obj = Lexeme.objects.get(pk=self.kwargs['pk'])
kwargs = super().get_form_kwargs()
kwargs['initial']['case_sensitive'] = lex_obj.lexicon.case_sensitive
kwargs['initial']['diacritics'] = lex_obj.lexicon.diacritics
return kwargs
Is that what you want? I have not tested but, I have used similar thing on my project. Let me know if works or not.
I finally found the way to go. It was just basic initial setting of field, no need to touch to forms.py, models.py nor the html template.
I passed data to my form like this:
lexeme_form = LexemeForm(initial={'case_sensitive': lexicon.case_sensitive, 'diacritics': lexicon.diacritics})
use Ajax at template to change the initial value of "case_sensitive" and "diacritics" when Lexicon changed, and abstract model can be used to reduce repeat lines :
class BaseLex(models.Model):
case_sensitive = models.BooleanField(default=True)
diacritics = models.BooleanField(default=True)
class Meta:
abstract = True
class Lexicon(BaseLex):
# without `case_sensitive` and `diacritics' fields
...
class Lexeme(BaseLex):
# without `case_sensitive` and `diacritics' fields
lexicon = models.ForeignKey(Lexicon, on_delete=models.CASCADE)
...

Autopopulate a set of generic many to many fields in Django?

I'm trying to combine this answer and this one, with a bit of for looping.
On creating a character, I want to add all possible skills with a value of 0 but I'm getting confused on how to follow the above answers.
I have this mixin:
class CrossCharacterMixin(models.Model):
cross_character_types = models.Q(app_label='mage', model='mage')
content_type = models.ForeignKey(ContentType, limit_choices_to=cross_character_types,
null=True, blank=True)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
abstract = True
(eventually, the cross_character_types will be expanded)
And this model:
class CharacterSkillLink(Trait, CrossCharacterMixin):
PRIORITY_CHOICES = (
(1, 'Primary'), (2, 'Secondary'), (3, 'Tertiary')
)
skill = models.ForeignKey('SkillAbility')
priority = models.PositiveSmallIntegerField(
choices=PRIORITY_CHOICES, default=None)
speciality = models.CharField(max_length=200, null=True, blank=True)
def __str__(self):
spec_string = " (" + self.speciality + ")" if self.speciality else ""
return self.skill.skill.label + spec_string
What I've started writing is this, on the NWODCharacter model:
def save(self, *args, **kwargs):
if not self.pk:
character_skills_through = CharacterSkillLink.content_object.model
CharacterSkillLink.objects.bulk_create([
[character_skills_through(skill=SkillAbility(
skill), content_object=self) for skill in SkillAbility.Skills]
])
super(NWODCharacter, self).save(*args, **kwargs)
This doesn't work as I don't think I'm passing in the right objects.
Based on this answer though:
from django.db import models
class Users(models.Model):
pass
class Sample(models.Model):
users = models.ManyToManyField(Users)
Users().save()
Users().save()
# Access the through model directly
ThroughModel = Sample.users.through
users = Users.objects.filter(pk__in=[1,2])
sample_object = Sample()
sample_object.save()
ThroughModel.objects.bulk_create([
ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])
In this situation, what is my ThroughModel? Is it CharacterSkillLink.content_object.model ?
How do I do this in my scenario? I'm sorry if this is trivial, but I'm struggling to get my head round it.
It looks to me like CharacterSkillLink itself is your through model in this case... it generically joins a content type to a SkillAbility
If you think about it, it also makes sense that if you're doing a bulk_create the objects that you pass in must be of the same model you're doing a bulk_create on.
So I think you want something like this:
def save(self, *args, **kwargs):
initialise_skill_links = not self.pk
super(NWODCharacter, self).save(*args, **kwargs)
if initialise_skill_links:
CharacterSkillLink.objects.bulk_create([
CharacterSkillLink(
skill=SkillAbility.objects.get_or_create(skill=skill)[0],
content_object=self
)
for skill in SkillAbility.Skills
])
Note you had too many pairs of [] inside your bulk_create.
Also I think you should use SkillAbility.objects.get_or_create()... for a foreign key you need the related object to exist. Just doing SkillAbility() won't fetch it from the db if it already exists and won't save it to the db if it doesn't.

Auto Incrementing natural keys with django / postgres

Let me preface this in saying that I'm a UI dev who's trying to branch out into more backend coding, so excuse me if my verbiage is off at all. This is could be a duplicate, but i'm not sure what on god's good green earth i'm even supposed to call what i want to do.
Basically, I have categories, and images. I need to label each image with an acronym of the category it belongs to, and increment a sku after.
For Example, the following images would be automatically labeled like...
ABC-1
ABC-2
DEF-1
DEF-2
DEF-3
ABC-3*
*note: I want it to increment the ID based on the category, not the total # of images
How would I achieve this in idiomatic Django?
Models:
class Group(models.Model):
title = models.CharField(max_length=200)
abbv = models.CharField(max_length=200)
urlified = models.CharField(max_length=200)
description = models.TextField(blank=True)
hidden = models.BooleanField()
def __unicode__(self):
return self.title
class Photo(models.Model):
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
class Meta:
ordering = ('pub_date',)
If you want true composed primary keys, you might want to use django-compositepks, but that is not ideal. You might be better off breaking DRY and recording the number (see the category_auto_key field and default).
Transactions will solve it this way:
from django.db import transaction
class Group(models.model):
# your fields
img_count = models.IntegerField()
#transaction.atomic
def next_sku(self):
self.img_count += 1
self.save()
return self.img_count
class Photo(models.Model):
# your fields
category_auto_key = models.IntegerField(editable=False)
def category_image(self):
return self.group.abbv+"-"+str(self.category_auto_key)
def save(self, *args, **kwargs):
if not self.category_auto_key:
self.category_auto_key = self.group.next_sku()
super(Photo, self).save(*args, **kwargs)
When you need this in your templates, just enclose it in double brackets:
{{ photo.category_image }}
I'm curious if you just want to generate and store the acronym and sku in a text field, or if you are trying to create relationships between your image categories?
If the later, I would look for a different approach.
If the former, i would use a customized set or save method (hook?) for your image model. It will need do a small one time lookup to count the number of acronym already existing, but I wouldn't worry about the performance too much.
Wasn't sure how to do this exactly in Django off the top of my head, but it looks like the accepted answer works similarly. Anyways, here is my attempt at setting a Model Field during save. Be warned this in untested.
After looking into it more I think that Beltiras' solution is better
class Photo(models.Model):
# simple column definitions
group = models.ForeignKey(Group)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
pub_date = models.DateTimeField(auto_now_add = True, blank=True)
image = models.ImageField(max_length=100)
# new column for storing abbv sku
category_label = models.CharField(max_length=200)
# save override
def save(self, *args, **kwargs):
# hopefully only set category_label on first save, not sure this
# works, open to other ideas
if (self.pk is None):
count = Photo.objects.filter(group=self.group).count()
label = self.group.abbv + '-' + count
setattr(self, 'category_label', label)
# call the super class' save method
super(Photo, self).save(*args, ** kwargs)
The part I am least sure about is:
count = Photo.objects.filter(group=self.group).count()
The idea is to query the photos table for photos in the same group and count them. This may need to be replaced with a direct SQL call or done some other way. Let me know what you find.

Basic Python, Django, DRY - calling a method from a (model) class

I'm new to Python and Django. I have a basic python/django ORM question that's bothering me. I have two models and they have a show_image function that's repeated. That's no good.
class Dinner(models.Model):
title = models.CharField(max_length=200)
is_approved = models.BooleanField()
hero = models.ImageField(upload_to="heros", blank=True)
def show_image(self):
image_url = None
if self.hero is not None:
image_url = """<img src="{0}{1}" />""".format(BASE_URL, self.hero)
return image_url
show_image.short_description = "Thumbnail"
show_image.allow_tags = True
class Speaker(models.Model):
title = models.CharField(max_length=200)
biography = models.TextField(blank=True)
headshot = models.ImageField(upload_to="headshots", blank=True)
def show_image(self):
image_url = None
if self.headshot is not None:
image_url = """<img src="{0}{1}" />""".format(BASE_URL, self.headshot)
return image_url
show_image.short_description = "Thumbnail"
show_image.allow_tags = True
Seems simple enough- I decided to start experimenting. I created a method in models.py...
def test(obj):
print obj
then in my models I tried:
test(self.hero)
and got this (instead of the value):
django.db.models.fields.files.ImageField
How do I get the value out of this so I can check if the ImageField has been populated?
edit:
class Speaker(models.Model):
title = models.CharField(max_length=200)
biography = models.TextField(blank=True)
headshot = models.ImageField(upload_to=upload_to, blank=True)
test(headshot)
def show_image(self):
image_url = None
if self.headshot is not None:
image_url = """<img src="{0}{1}" />""".format(BASE_URL, self.headshot)
return image_url
show_image.short_description = "Thumbnail"
show_image.allow_tags = True
You're calling that test method at class level, which makes no sense. That means it's executed at the time that the model class is defined, which is why you see the field class. There's a whole lot of metaclass stuff that happens when models are defined, so that when you get an instance you see the value, not the field class - but that hasn't happened at the point you're calling the method.
In any case, you need to call that with an instance of the model, so that there is actually a value to deal with.
I suspect you're fairly new to Python, so here's a tip: you can inspect all this stuff from the Python shell. Start ./manage.py shell, then import your models, instantiate one (or get it from the db), then you can examine it with dir() and so on. Much more efficient than writing debug functions in your code.

Categories

Resources