Django 3 Dynamic Default Value in Model - python

There are similar questions but none recent and based on Django 3 so here it goes:
I am using py nanoid to generate some unique IDs so in my models I have
from nanoid import generate
and then
class Work(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published = models.BooleanField(False)
date = models.DateTimeField()
nanoid = models.CharField(max_length=15, default = generate(size=10) )
def __str__ (self):
return self.title
my aim is to ensure that a new nanoid is generated each time a new work post is added. At the moment is the same repeated each time I try to add a new Work via admin.
I read some replies about creating a custom class but I am a bit at a loss here!

If you pass a function as a default it must be one that takes no arguments. You can use functools.partial to turn generate into a function that takes no args and has size=10 set as the default
from functools import partial
class Work(models.Model):
...
nanoid = models.CharField(max_length=15, default=partial(generate, size=10))

You can override the save method on your model to do it, also you have to create a helper function to check if the nanoid on your model already exists in your persistent storage.
class Work(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published = models.BooleanField(False)
date = models.DateTimeField()
nanoid = models.CharField(max_length=15)
def save(self, *args, **kwargs):
nanoid = generate_id()
super(Work, self).save(*args, **kwargs)
def __str__ (self):
return self.title
# Helpers
def generate_id():
"""
Generate nanoid unique code.
"""
n_id = generate(size=10)
while not is_available_code(n_id):
n_id = generate(size=10)
return n_id
def is_available_id(id):
"""
Validate Id.
"""
return not id_is_nanoid(id)
def id_is_nanoid(id):
return Work.objects.filter(nanoid=id).exists()

Related

Django models Many to one not able to access all objects in a foreign key field

I might be confused however when I check the django-admin panel I can see (will provide a screenshot) , over 50 models attached to my primary model, however whenever I make a query in the code and try to access the set associated with the field I only ever get one entry. I am trying to make one query and check all models associated with the field.
My models.py code:
class TokenData(models.Model):
name = models.CharField(max_length=200)
contract_address = models.CharField(max_length=200,primary_key=True, unique=True)
def save(self, *args, **kwargs):
#print('save() is called.')
super(TokenData, self).save(*args, **kwargs)
def __str__(self):
return self.name
class WalletData(models.Model):
address = models.CharField(max_length=200,primary_key=True,unique=True)
contract_address = models.ForeignKey(to=TokenData,on_delete=models.CASCADE,)
last_bitquery_scan = models.CharField(max_length=200)
def __str__(self):
return self.address
I am trying to access the model like so :
WalletData.objects.filter(address=address)
One thing I noticed is when I create a variable containing the filter, and access the contract_address in the WalletData model, I can endlessly query myself in a circle for lack of a better word, by accessing the set and executing a get against it.
I am just wanting to access all 50 models like shown below
The 50+ objects you see in the drop down are just all the TokenData objects in the DB listed by the name.
From what i can understand what you want is all the TokenData associated with a particular WalletData address, if so then for a WalletData object w you can do
TokenData.objects.filter(contact_address = w.contact_address).values_list('name',flat=True)
This gives you all the TokenData name associated to a WalletData address.
I ended up figuring it out here was my fix:
Models.py
class TokenData(models.Model):
name = models.CharField(max_length=200)
contract_address = models.CharField(max_length=200,primary_key=True, unique=True)
def save(self, *args, **kwargs):
#print('save() is called.')
super(TokenData, self).save(*args, **kwargs)
def __str__(self):
return self.name
class WalletData(models.Model):
address = models.CharField(max_length=200,primary_key=True,unique=True)
#contract_address = models.ForeignKey(to=TokenData,on_delete=models.CASCADE,)
last_bitquery_scan = models.CharField(max_length=200)
def __str__(self):
return self.address
class WalletHolding(models.Model):
token = models.ForeignKey(to=TokenData,on_delete=CASCADE)
wallet = models.ForeignKey(to=WalletData,on_delete=CASCADE)
def __str__(self):
return self.token
I had to add another model i.e WalletHolding, this way when I query the WalletHolding by the wallet field I get all tokens associated with the wallet using a query like this:
WalletHolding.objects.filter(wallet=address).values_list('token',flat=True)
Result:

How to go about splitting up multiple select in a Django form?

To make a more user-friendly interface on my website I would like to cut up a multiple select in a couple of multiple selects based on an attribute. How would you go about this?
Models.py
I basically have 4 classes as visualized below. I have a schedule to which tasks can be related. Furthermore the user needs to be able to assign user fields per schedule. An example of a user field could be 'topic' or 'priority: whatever the user decides. Each user field can have multiple user field values. For example, values for 'priority' could be 'Urgent', 'Normal' and 'No rush'. Each task should then have a value assigned to it. Ideally I would like it only to have one possible value per user field. So following the examples above a task can not be 'urgent' and 'normal' at the same time. This is another thing I need to figure out, so any hints are welcome, but outside the scope of this question.
class Schedule(models.Model):
title = models.CharField(max_length=50)
def __str__(self):
return self.title
class Tasks(models.Model):
schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
title = models.TextField()
start = models.DateTimeField()
end = models.DateTimeField()
userfieldvalues = models.ManyToManyField("UserFieldValues", blank=True)
def __str__(self):
return self.title
class UserField(models.Model):
name = models.CharField(max_length=50)
schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
def __str__(self):
return self.name
class UserFieldValues(models.Model):
value = models.CharField(max_length=50)
userfield = models.ForeignKey(UserField, on_delete=models.CASCADE, related_name="values")
def __str__(self):
return self.value
Template and forms.py
I created a form:
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ("title", "start", "end", "userfieldvalues")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = 'Task Title'
self.fields['title'].widget.attrs['rows'] = 1
self.fields['start'].widget.format = '%H:%Mh, %d-%m-%Y'
self.fields['start'].input_formats = ['%H:%Mh, %d-%m-%Y']
self.fields['end'].widget.format = '%H:%Mh, %d-%m-%Y'
self.fields['end'].input_formats = ['%H:%Mh, %d-%m-%Y']
In my template I render the form. For the user field values it now shows all the options is a multiple select. I would like to create a multiple select per UserField, but I don't know how to go about.

how to avoid circular references and write DRY code in django

Whenever I create DRY functions that I can reuse later and then use them in models, I get circular references;
For example:
I have the following models:
from social.services import get_top_viewed_posts
class Post(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
title = models.CharField('Post Title', max_length=255)
class ActivityUpdateEmail(models.Model):
sent = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
def send(self):
posts = get_top_viewed_posts()
My top viewed posts function is another file called services.py so I can access it other places. It looks like:
from social.models import Post
def get_top_viewed_posts():
posts = Post.objects.filter(
pk__in=popular_posts_ids,
).order_by(
'-created_at'
)
return posts
Then I get the error:
services.py", line 1, in <module>
from social.models import Post
ImportError: cannot import name 'Post'
If I change it to:
transactions = Action.objects.filter(
content_type__pk=35,
created_at__gte=start_date,
).values_list('object_id', flat=True)
popular_posts_ids = []
popular_posts = Counter(transactions).most_common()[:result_amount]
for dic in popular_posts:
popular_posts_ids.append(dic[0])
class ActivityUpdateEmail(models.Model):
sent = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
def send(self):
posts = Post.objects.filter(
pk__in=popular_posts_ids,
).order_by(
'-created_at'
)
This works no problem.
How can I use this dry approach of abstracting functionality, and then being able to use them in my models?
The error occurs because when you import get_top_viewed_posts at the top of models.py the Post model is not declared yet.
You have a few alternatives.
Move the import from the top of models.py to inside the method
def send(self):
from social.services import get_top_viewed_posts
posts = get_top_viewed_posts()
Don't worry about performance, imports are cached - but if you use it in other methods it may be tedious to repeat the same import over and over.
Abstract the class
Make the function more generic passing the model as an argument, this way you don't need to import the model in the top of the services.py file:
def get_top_viewed_model(model, popular_ids, order_by='-created_at'):
return model.objects..filter(
pk__in=popular_ids,
).order_by(
order
)
Then:
def send(self):
posts = get_top_viewed_model(type(self), popular_posts_ids)
# at other places
get_top_viewed_model(Posts, popular_posts_ids)
Use a custom manager
Create a custom manager with a top_viewed method:
class TopViewedManager(models.Manager):
def __init__(self, order='-created_at', **kwargs):
self._order = order
self._filter = kwargs
def top_viewed(self):
return self.get_queryset().filter(**self._filter).order_by(self._order)
class Post(models.Model):
...
objects = TopViewedManager(pk__in=popular_posts_ids)
Then just use this where you would use get_top_viewed_model:
Post.objects.top_viewed()
This manager is quite generic so you can use it with any model, filter and order you want.
Probably there are other alternatives and it is a matter of personal taste.

How to customize Model or Manager that change the data format in django?

I hava an Article model ,contains a title column,which can be stored mix with white space,what i want is that ,every time i query an article,space in title content could be repaced with dash,for url friendly.
models.py:
class Article(models.Model):
STATUS = (
(0,'on'),
(1,'off')
)
#id = models.IntegerField(primary_key=True,help_text='primary key',auto_created=True)
category = models.ForeignKey(Category,related_name='articles', help_text='foreigner key reference Category')
#author = models.ForeignKey(myadmin.User, help_text='foreigner key reference myadmin User')
title = models.CharField(max_length=100, help_text='article title')
description = models.TextField(help_text='article brief description')
content = models.TextField(help_text='article content')
like = models.IntegerField(default=0,help_text='like numbers')
secretcode = models.CharField(max_length=512,help_text='who has the code can scan')
status = models.IntegerField(choices=STATUS,help_text='status of the article')
createtime = models.DateTimeField(auto_now_add=True,help_text='time that first created')
modifytime = models.DateTimeField(auto_now=True,help_text='time when modified')
articles = models.Manager()
def __str__(self):
return self.title
class Meta:
db_table = 'article'
my view.py:
def get(self,request):
offset = int(request.GET.get('offset', 0))
category = request.GET.get('category')
end = offset+10
articlecatalogs = Article.articles.filter(category__name=category)[offset:end]
i was thinking creating a custom Manager and define a method to transform the data,but the query conditions needed are from request,in here,i don't know how to do it ?can someone help me?
I think you have to use slug filed as well and overwrite your save method for save slug something like this :
class Article(models.Model):
slug = models.SlugField("slug")
category = models.ForeignKey(Category,related_name='articles', help_text='foreigner key reference Category')
-- more fields --
def __str__(self):
return self.title
def get_absolute_url(self):
return self.slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.title.strip(" ").replace(' ','-')
super(Article, self).save(self, *args, **kwargs)
#property
def get_title(self):
""" write python code for remove extra spaces so can can dispay your tile in html and call this method with instance when you want to print title """
return new_title_without_extra_spaces
for details page you can use slug value for get a instance. Hope this would be helpful to you.

Django call a funtion in model save method and call another function to fill value

I have a model in Django
class File(models.Model):
NORMAL = 'normal'
AD = 'ad'
FILE_TYPE_CHOICES = (
(NORMAL, 'Normal'),
(AD, 'Ad'),
)
file_name = models.CharField(max_length=50)
file_type = models.CharField(max_length=10, default=NORMAL,
choices=FILE_TYPE_CHOICES)
file_path = models.FileField('file', upload_to='documents/%Y/')
duration = models.IntegerField()
#prompt_id = models.CharField(max_length=50)
def __unicode__(self):
return self.file_name
def save(self, *args, **kwargs):
self.prompt_id = IvrUploadAudioFile(self.file_path)
super(File, self).save(*args, **kwargs)
i have a column prompt_id in file table, but I don't want to show prompt_id field in add/edit.
i Want to insert prompt_id value, which a function IvrUploadAudioFile will return behind the scene. so i am overriding save method here.
so where to write IvrUploadAudioFile function.
how to write this situation
Django models are just normal Python classes, and you can add as many helper methods as you want to the class:
class File(models.Model):
# -- normal stuff
prompt_id = models.CharField(max_length=50, editable=False)
def ivr_upload_audio_file(self):
# do something with self.file_path
return something
def save(self, *args, **kwargs):
self.prompt_id = self.ivr_upload_audio_file()
super(File, self).save(*args, **kwargs)
As for your other question, about not showing the field - you can add editable=False to hide the field in the admin and any ModelForms - however, if you just want to hide it in the admin, but show it in other forms - you can customize your admin to not show that field:
#admin.register(File)
class FileAdmin(admin.ModelAdmin):
fields = ('file_name', 'file_path', 'file_type', 'duration',)
You can write it anywhere as long as it can be imported into your project.
from <insert_path_here> import IvrUploadAudioFile # Function written somewhere else. You need to insert the correct path here
from django.db import models
class File(models.Model):
NORMAL = 'normal'
AD = 'ad'
FILE_TYPE_CHOICES = (
(NORMAL, 'Normal'),
(AD, 'Ad'),
)
file_name = models.CharField(max_length=50)
file_type = models.CharField(max_length=10, default=NORMAL,
choices=FILE_TYPE_CHOICES)
file_path = models.FileField('file', upload_to='documents/%Y/')
duration = models.IntegerField()
#prompt_id = models.CharField(max_length=50, editable = False)
def __unicode__(self):
return self.file_name
def save(self, *args, **kwargs):
self.prompt_id = IvrUploadAudioFile(self.file_path)
super(File, self).save(*args, **kwargs)
To make sure that the prompt_id field is not editable in the admin view, set the editable = False attribute when defining the prompt_id field in the model.
If you're talking about wanting to hide the field in Django admin, you need to set it as editable=False: https://docs.djangoproject.com/en/1.7/ref/models/fields/#editable
class File(models.Model):
. . .
prompt_id = models.CharField(max_length=50, editable=False)
I'm not sure what you intend to do by passing self.file path to your IvrUploadAudioFile class, but the code, as written, will not return the primary key of that object.

Categories

Resources