Auto generate and save data to SlugField - django

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()

Related

Django models.py (API result) - retrieve current post to add api result

I'm new to Django I got an issue. I don't know how to retrieve the current post inside of models.py. I've tried different way for that.
'QuerySet' object has no attribute 'aliments'
or no error and no add to Post from ListAliments
get_object_or_404(Post, id=kwargs['id'])
here is my models.py
class ListAliments(models.Model):
name = models.CharField(max_length=40, unique=True)
slug = models.SlugField(editable=False)
status = models.IntegerField(choices=STATUS, default=1)
def save(self, *args,**kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.name))
super(ListAliments, self).save(*args, **kwargs)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=190)
url_image = models.URLField(max_length=200, default=None)
aliments = models.ManyToManyField('ListAliments',blank=True, related_name='listaliments_post')
...
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.title))
super(Post, self).save(*args, **kwargs) -> First save for Post which has not no ID
...
if self.url_image:
request = ...
response = ...
if response:
names = []
for concept in response.outputs[0].data.concepts:
current_aliments = ListAliments.objects.filter(name=concept.name)
current_post = Post.objects.filter(url_image=self.url_image) #get_object_or_404(Post, id=kwargs['id'])
if current_aliments.count()<1:
create_aliments = self.aliments.create(name=concept.name)
current_post.aliments.add(create_aliments)
else:
existed_aliments = ListAliments.objects.get(name=concept.name)
current_post.aliments.add(existed_aliments)
super().save(*args, **kwargs)
Post.objects.filter(url_image=self.url_image) returns QuerySet
in order to get object call first so Post.objects.filter(url_image=self.url_image).first(); note that you can get None

Unable to get repr for queryset when call from Viewset

I am trying to call a queryset for a model to add to my serializer using objects.all() but the debug said Unable to set repr for <class 'django.db.models.query.Queryset'>
Here is my viewset
class TransactionReceiptViewSet(viewsets.GenericViewSet,
viewsets.mixins.RetrieveModelMixin,
viewsets.mixins.ListModelMixin):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = base_serializers.TransactionReceiptSerializer
queryset = models.TransactionReceipt.objects.all()
def get_queryset(self):
user = self.request.user
return models.TransactionReceipt.objects.filter(user_profile=user)
def retrieve(self, request, *args, **kwargs):
response = super(TransactionReceiptViewSet, self).retrieve(request, *args, **kwargs)
receipt = self.get_object()
serializer = self.get_serializer(receipt)
product_qs = models.ProductReceipt.objects.all()
products_data = base_serializers.ProductReceiptSerializer(
product_qs, many=True)
serializer.data['products'] = products_data
return Response(serializer.data)
and here is the model I tried to call for
class ProductReceipt(models.Model):
id = models.AutoField(primary_key=True)
amount = models.IntegerField(default=1)
product = models.ForeignKey(Product, on_delete=models.DO_NOTHING, default=None)
created_date = models.DateTimeField('Date of purchase', auto_now=True)
transaction_receipt = models.ForeignKey(TransactionReceipt, on_delete=models.CASCADE)
price = models.IntegerField(default=0)
def __str__(self):
return "object created"
def __init__(self):
super().__init__()
self.product = Product()
self.transaction_receipt = TransactionReceipt()
def save(self, **kwargs):
self.amount = 1
self.created_date = datetime.now()
self.price = self.product.price_tag.price
When I debug the API, it said that Unable to set repr for <class 'django.db.models.query.Queryset'> in product_qs and nothing is returned
Edit:
I think that the Model have to do something with this. I have tried to create a ModelViewSet for ProductReceipt and it worked fine. But when i try to make the query manually. it somehow broke the mapping to the foreign key??? and return nothing?
Okey lets check a couple of things. First of all in your ProductReceipt class the def save(self, **kwargs) method is not calling super and that's a huge problem because the objects are not gonna be saved ever. Secondly, in the ProductReceipt class the def __init__(self) method you are assigning a new Product and a new TransactionReceipt to your ProductReceipt instance, but you are not setting the data of this two objects neither saving them in any place (maybe you should assign them inside save method and save them before calling super?).
Try this corrections and if it keeps not working we will another possible errors.
Finally, def __str__(self) is a string representation of your object, it will be a good implementation for example:
def __str__(self):
return self.product.name + ' x' + str(amount)
Turn out that the product field is not set to null=True. And i have old data with that field point to nothing. There for it break when trying to query from the database. In short, i didn't migrate properly.

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()

Override save method for every class in models.Model

I need to get every class, that inherits models.Model to have created and updated field. I can achieve this by adding custom save method to every field,
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
self.modified = timezone.now()
return super(`models.Model`, self).save(*args, **kwargs)
but this violates Don'tRepeatYourself rule.
I've tried to override models.Model:
class LogModel(models.Model):
created = models.DateTimeField(editable=False)
updated = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
self.updated = timezone.now()
return super(LogModel, self).save(*args, **kwargs)
and use LogModel instead of models.Model, but this failed with error E006(
The field 'x' clashes with the field 'x' from model 'y.logmodel'.
EDIT
My main question is how to add a custom specific field to all models in my models.py
Your base model must be abstract:
class LogModel(models.Model):
class Meta:
abstract = True
created = models.DateTimeField(editable=False)
updated = models.DateTimeField()
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
# Use self._state.adding to check if this is a new instance,
# ID not being empty is not a guarantee that the instance
# exists in the database
# and if `update_fields` is passed, you must add the fields to the
# list or they won't be saved in the database.
if force_insert or self._state.adding:
self.created = timezone.now()
if update_fields and 'created' not in update_fields:
update_fields.append('created')
self.updated = timezone.now()
if update_fields and 'updated' not in update_fields:
update_fields.append('updated')
return super(LogModel, self).save(*args, **kwargs)
However, if you override the save() method, this means it won't be editable in any form. If this is what you want, then you are better to use auto_now and auto_now_add:
class LogModel(models.Model):
class Meta:
abstract = True
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Instead of overriding save method, you could define auto_now_add and auto_now parameters in the Model field like:
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
For more information on these parameters, you can check the django docs.
It can be done by defining Abstract Base Model and define save method there and create all the models by inheriting from the abstract class. e.g.
class MyAbstractModel(models.Model):
created = models.DateTimeField(editable=False)
updated = models.DateTimeField()
def save(self, *args, **kwargs):
if self._state.adding:
self.created = timezone.now()
self.updated = timezone.now()
return super(LogModel, self).save(*args, **kwargs)
class Meta:
abstract = True
and create child model class from it:
class Record(MyAbstractModel):
pass
Unfortunately, using update_fields in .save() does not trigger fields with auto_now, so I overridden save method this way:
class BaseModel(models.Model):
class Meta:
abstract = True
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
_base_update_fields = ['updated_at']
def save(
self, force_insert=False, force_update=False,
using=None, update_fields=None
):
_update_fields = None
if update_fields is not None:
_update_fields = self._base_update_fields + update_fields
super().save(
force_insert=force_insert, force_update=force_update,
using=using, update_fields=_update_fields
)

How to create a unique slug in Django

I am trying to create a unique slug in Django so that I can access a post via a url like this:
http://www.example.com/buy-a-new-bike_Boston-MA-02111_2
The relevant models:
class ZipCode(models.Model):
zipcode = models.CharField(max_length=5)
city = models.CharField(max_length=64)
statecode = models.CharField(max_length=32)
class Need(models.Model):
title = models.CharField(max_length=50)
us_zip = models.CharField(max_length=5)
slug = ?????
def get_city():
zip = ZipCode.objects.get(zipcode=self.us_zip)
city = "%s, %s %s" % (zip.city, zip.statecode, zip.zipcode)
return city
A sample ZipCode record:
zipcode = "02111"
city = "Boston"
statecode = "MA"
A sample Need record:
title = "buy a new bike"
us_zip = "02111"
slug = "buy-a-new-bike_Boston-MA-02111_2" (desired)
Any tips as to how to create this unique slug? Its composition is:
Need.title + "_" + Need.get_city() + "_" + an optional incrementing integer to make it unique. All spaces should be replaced with "-".
NOTE: My desired slug above assumes that the slug "buy-a-new-bike_Boston-MA-02111" already exists, which is what it has the "_2" appended to it to make it unique.
I've tried django-extensions, but it seems that it can only take a field or tuple of fields to construct the unique slug. I need to pass in the get_city() function as well as the "_" connector between the title and city. Anyone solved this and willing to share?
Thank you!
UPDATE
I'm already using django-extensions for its UUIDField, so it would be nice if it could also be usable for its AutoSlugField!
I use this snippet for generating unique slug and my typical save method look like below
slug will be Django SlugField with blank=True but enforce slug in save method.
typical save method for Need model might look below
def save(self, **kwargs):
slug_str = "%s %s" % (self.title, self.us_zip)
unique_slugify(self, slug_str)
super(Need, self).save(**kwargs)
and this will generate slug like buy-a-new-bike_Boston-MA-02111 , buy-a-new-bike_Boston-MA-02111-1 and so on. Output might be little different but you can always go through snippet and customize to your needs.
My little code:
def save(self, *args, **kwargs):
strtime = "".join(str(time()).split("."))
string = "%s-%s" % (strtime[7:], self.title)
self.slug = slugify(string)
super(Need, self).save()
If you are thinking of using an app to do it for you, here is one.
https://github.com/un33k/django-uuslug
UUSlug = (``U``nique + ``U``code Slug)
Unicode Test Example
=====================
from uuslug import uuslug as slugify
s = "This is a test ---"
r = slugify(s)
self.assertEquals(r, "this-is-a-test")
s = 'C\'est déjà l\'été.'
r = slugify(s)
self.assertEquals(r, "c-est-deja-l-ete")
s = 'Nín hǎo. Wǒ shì zhōng guó rén'
r = slugify(s)
self.assertEquals(r, "nin-hao-wo-shi-zhong-guo-ren")
s = '影師嗎'
r = slugify(s)
self.assertEquals(r, "ying-shi-ma")
Uniqueness Test Example
=======================
Override your objects save method with something like this (models.py)
from django.db import models
from uuslug import uuslug as slugify
class CoolSlug(models.Model):
name = models.CharField(max_length=100)
slug = models.CharField(max_length=200)
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name, instance=self)
super(CoolSlug, self).save(*args, **kwargs)
Test:
=====
name = "john"
c = CoolSlug.objects.create(name=name)
c.save()
self.assertEquals(c.slug, name) # slug = "john"
c1 = CoolSlug.objects.create(name=name)
c1.save()
self.assertEquals(c1.slug, name+"-1") # slug = "john-1"
Here are a couple functions that I use. You pass in the model instance and the desired title into unique_slugify which will add the slug if it doesn't exist, otherwise it will continue trying to append a 4 digit random string until it finds a unique one.
import string
from django.utils.crypto import get_random_string
def unique_slugify(instance, slug):
model = instance.__class__
unique_slug = slug
while model.objects.filter(slug=unique_slug).exists():
unique_slug = slug + get_random_string(length=4)
return unique_slug
I usually use it by overriding the model save method.
class YourModel(models.Model):
slug = models.SlugField()
title = models.CharField()
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.title))
super().save(*args, **kwargs)
This is the simple and small code i am using for generating unique slug,
you only need one field to create your unique slug field
from random import randint
def save(self, *args, **kwargs):
if Post.objects.filter(title=self.title).exists():
extra = str(randint(1, 10000))
self.slug = slugify(self.title) + "-" + extra
else:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
I hope you like this.
This is a simple implementation that generate the slug from the title, it doesn't depend on other snippets:
from django.template.defaultfilters import slugify
class Article(models.Model):
...
def save(self, **kwargs):
if not self.slug:
slug = slugify(self.title)
while True:
try:
article = Article.objects.get(slug=slug)
if article == self:
self.slug = slug
break
else:
slug = slug + '-'
except:
self.slug = slug
break
super(Article, self).save()
Django provides a SlugField model field to make this easier for you. Here's an example of it in a "blog" app's
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField(blank=True)
slug = models.SlugField(unique=True)
#models.permalink
def get_absolute_url(self):
return 'blog:post', (self.slug,)
Note that we've set unique=True for our slug field — in this project we will be looking up posts by their slug, so we need to ensure they are unique. Here's what our application's views.py might look like to do this:
from .models import Post
def post(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog/post.html', {
'post': post,
})
from django.utils.text import slugify Helps a lot and has quite clear Concepts.
Here one example on How to auto-generate slug by using from django.utils.text import slugify
utils.py
from django.utils.text import slugify
import random
import string
# Random string generator
def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
# Unique Slug Generator
def unique_slug_generator(instance, new_slug=None):
"""
It assumes your instance has a model with a slug field and a title character (char) field.
"""
if new_slug is not None:
slug = new_slug
else:
slug = slugify(instance.title)
Klass = instance.__class__
qs_exists = Klass.objects.filter(slug=slug).exists()
if qs_exists:
new_slug = "{slug}-{randstr}".format(slug=slug, randstr=random_string_generator(size=4))
return unique_slug_generator(instance, new_slug=new_slug)
return slug
models.py
from django.db.models.signals import pre_save # Signals
# import the unique_slug_generator from .utils.py
from .utils import unique_slug_generator
class Product(models.Model):
title = models.CharField(max_length=120)
# set blank to True
slug = models.SlugField(blank=True, unique=True)
def product_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(product_pre_save_receiver, sender=Product)
Django documentation explains Django.utils.text import slugify to generate slug automatically. You can read more detail here
After implementing the code, while creating product, you may leave the slug field blank, which will be further aquired with auto generated slug for the product which will be unique in this case.
Hi can you tried this function
class Training(models.Model):
title = models.CharField(max_length=250)
text = models.TextField()
created_date = models.DateTimeField(
auto_now_add=True, editable=False, )
slug = models.SlugField(unique=True, editable=False, max_length=250)
def __unicode__(self):
return self.title
def get_unique_slug(id,title,obj):
slug = slugify(title.replace('ı', 'i'))
unique_slug = slug
counter = 1
while obj.filter(slug=unique_slug).exists():
if(obj.filter(slug=unique_slug).values('id')[0]['id']==id):
break
unique_slug = '{}-{}'.format(slug, counter)
counter += 1
return unique_slug.
def save(self, *args, **kwargs):
self.slug =self.get_unique_slug(self.id,self.title,Training.objects)
return super(Training, self).save(*args, **kwargs)
def get_slug(self):
slug = slugify(self.title.replace("ı", "i"))
unique = slug
number = 2
while Model.objects.filter(slug=unique).exists():
unique = "{}-{}".format(slug, number)
number += 1
return unique
Best solution for me:
def get_slug(self):
slug = slugify(self.title)
unique_slug = slug
number = 1
while Recipe.objects.filter(slug=unique_slug).exists():
unique_slug = f'{slug}-{number}'
number += 1
return unique_slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.get_slug()
return super().save(*args, **kwargs)
This code can generate slug like this:
string-slug
string-slug-1 (if previous alredy exists)
string-slug-2 (if previous alredy exists)
and so on...
class Need(models.Model):
title = models.CharField(max_length=50)
us_zip = models.CharField(max_length=5)
slug = models.SlugField(unique=True)
def save(self, **kwargs):
slug_str = "%s %s" % (self.title, self.us_zip)
super(Need, self).save()
Try this, worked out for me,welcome in advance:
class Parcel(models.Model):
title = models.CharField(max_length-255)
slug = models.SlugField(unique=True, max_length=255)
weight = models.IntegerField()
description = models.CharField(max_length=255)
destination = models.CharField(max_length=255)
origin = models.CharField(max_length=255)
def __str__(self):
return self.description
def save(self, *args, **kwargs):
if not self.slug:
t_slug = slugify(self.title)
startpoint = 1
unique_slug = t_slug
while Parcel.objects.filter(slug=unique_slug).exists():
unique_slug = '{} {}'.format(t_slug, origin)
origin += 1
self.slug = unique_slug
super().save(*args, **kwargs)