How to change slug if same string is stored in it? - django

I am working with slug on my model. Although, the entries for slug are not unique. When I try to go to a url containing slug, it says get() returned more than one object and I understand that it is because the entries are not unique. How am I supposed to change the slug a bit, if identical entries occur?
model
class Cabin(models.Model):
centre_name = models.ForeignKey(Centre, on_delete=models.CASCADE )
code = models.CharField(max_length=8, unique=True, default=unique_rand)
total_seats = models.IntegerField(blank='False')
category=models.CharField(max_length=100, default=False)
booked_date=models.DateField(blank='False')
released_date=models.DateField(blank='False')
price=models.IntegerField(blank=False, default=None)
slug = models.SlugField(unique=False,default=None,blank=True)
objects = UserManager()
def save(self, *args, **kwargs):
self.slug = slugify(self.category)
super(Client, self).save(*args, **kwargs)

First of all it might be better to set unique=True, such that this can never happen, furthermore you can :
class Cabin(models.Model):
centre_name = models.ForeignKey(Centre, on_delete=models.CASCADE )
code = models.CharField(max_length=8, unique=True, default=unique_rand)
total_seats = models.IntegerField(blank='False')
category=models.CharField(max_length=100, default=False)
booked_date=models.DateField(blank='False')
released_date=models.DateField(blank='False')
price=models.IntegerField(blank=False, default=None)
slug = models.SlugField(unique=True,default=None,blank=True)
objects = UserManager()
def save(self, *args, **kwargs):
slug = originalslug = slugify(self.category)
i = 0
while Cabin.objects.exist(slug=slug):
slug = '{}{}'.format(originalslug, i)
i += 1
self.slug = slug
super(Client, self).save(*args, **kwargs)
We here thus increment i until we found a slug that is not yet used.
Note that there exists an AutoSlugField in the django-extensions package [PyPi], that automates this slug procedure. For example:
from django.db import models
from django_extensions.db.fields import AutoSlugField
class Cabin(models.Model):
centre_name = models.ForeignKey(Centre, on_delete=models.CASCADE )
code = models.CharField(max_length=8, unique=True, default=unique_rand)
total_seats = models.IntegerField(blank='False')
category=models.CharField(max_length=100, default=False)
booked_date=models.DateField(blank='False')
released_date=models.DateField(blank='False')
price=models.IntegerField(blank=False, default=None)
slug = AutoSlugField(populate_from='category')
objects = UserManager()

Related

"<Post:>" needs to have a value for field "id" before this many-to-many relationship can be used

When i'm trying to add a Post through django admin i get an error that the Post im trying to add needs to have a value for field id. Do you have any idea why?
now = datetime.now()
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
class Meta:
verbose_name_plural = "categories"
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=100)
excerpt = models.CharField(max_length=200)
main_image = models.ImageField()
author = models.ForeignKey(users.models.CustomUser, on_delete=models.CASCADE, related_name='blog_posts', null=True)
content = models.TextField(null=True)
created_at = models.DateTimeField(editable=False)
updated_at = models.DateTimeField(editable=False)
category = models.ManyToManyField(Category, related_name='post_category')
class Meta:
ordering = ['-created_at']
def save(self, *args, **kwargs):
if not self.id:
self.created_at = now
self.updated_at = now
def __str__(self):
return self.title
You need to make a super().save(*args, **kwargs) call. Furthermore using a constant will not work: this will assign the time when you started the server, not the current time, so:
from django.utils.timezone import now
class Post(models.Model):
# …
def save(self, *args, **kwargs):
if not self.id:
self.created_at = now()
self.updated_at = now()
super().save(*args, **kwargs)
You furthermore do not need to specify logic to update the created_at and updated_at field, you can work with auto_now_add=True [Django-doc] and auto_now=True [Django-doc]:
class Post(models.Model):
# …
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# …
class Meta:
ordering = ['-created_at']
# no save override
def __str__(self):
return self.title

Slugify self.title + random numbers

I hope you're well. I've two questions for you:
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
url_image = models.URLField(max_length=200, default='SOME STRING')
author = models.ForeignKey(User, on_delete= models.CASCADE,related_name='blog_posts')
updated_on = models.DateTimeField(auto_now= True)
name_site = models.CharField(max_length=200, default='NA')
url_site = models.URLField(max_length=200, default='https://exemple.fr/')
content = models.TextField()
I. I want my title (unique=False) because I have some similar titles. So is it possible to save my slug (editable=False) with slugify with something like that:
slug_str = "%s %s" % (self.title, 4 random numbers like that 0476)
If anyone has a better idea, I'm interested in
Thanks a lot :) have a good holidays and take care
Here are a couple functions that I use. You pass in the model instance and the desired title into unique_slugify which will continue trying to add random strings until one is created that doesn't already exist.
import random
import string
def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def unique_slugify(instance, slug):
model = instance.__class__
unique_slug = slug
while model.objects.filter(slug=unique_slug).exists():
unique_slug = slug
unique_slug += random_string_generator(size=4)
return unique_slug
I usually use it by overriding the model save method.
class YourModel(models.Model):
slug = models.SlugField(max_length=200, unique=True)
title = models.CharField(max_length=200)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.title))
super().save(*args, **kwargs)
in my opinion, it doesn't make sens two posts may have the same title even with two different slugs, it even confuses readers and even worst it's very bad for SEO. i suggest you to avoid this path and try to review the logic
in models.py
class Post(models.Model):
title = models.CharField(_('title'), max_length=200,
unique=True, # title should be unique
help_text=_('The title of the entry.')
)
slug = models.SlugField(_('slug'), max_length=200,
unique=True, null=True, blank=True,
help_text=_(
'If blank, the slug will be generated automatically '
'from the given title.'
)
)
[..]
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title) # here you don't need to add random numbers since the title is already unque
super().save(*args, **kwargs)
in admin.py
class PostAdmin(admin.ModelAdmin):
list_display = ('title', .. )
readonly_fields = ('slug', .. ) # Add slug to read_only fields to make it not editable if you want

Django restrict foreign key options

So I have three models:
class Session(models.Model):
id = models.UUIDField('ID', default=uuid.uuid4, primary_key=True, editable=False)
start_time = models.TimeField('Start Time', default=None)
end_time = models.TimeField('End Time', default=None)
def __str__(self):
return "{}-{}".format(str(self.start_time), str(self.end_time))
class Slot(models.Model):
id = models.UUIDField('ID', default=uuid.uuid4, primary_key=True, editable=False)
timings = models.ForeignKey('Session', on_delete=models.DO_NOTHING, related_name='slot_timings')
available_counsellors = models.ManyToManyField(User, limit_choices_to={'role': 'COUNSELLOR'}, related_name='available_counsellors')
def __str__(self):
return str(self.timings)
class ChatSession(models.Model):
def get_access_code():
while True:
access_code = get_random_string(length=6, allowed_chars=('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'))
if not ChatSession.objects.filter(access_code=access_code).exists():
return access_code
id = models.UUIDField('ID', default=uuid.uuid4, primary_key=True, editable=False)
client = models.ForeignKey(User, limit_choices_to={'role': 'CLIENT'}, on_delete=models.DO_NOTHING, related_name='client_user')
counsellor = models.ForeignKey(User, limit_choices_to={'role': 'COUNSELLOR'}, on_delete=models.DO_NOTHING, related_name='counsellor_user')
access_code = models.CharField('Access Code', default=get_access_code, max_length=6)
topic = models.CharField('Topic', default=None, blank=True, null=True, max_length=255)
slot = models.ForeignKey(Slot, on_delete=models.DO_NOTHING, default=None)
def __str__(self):
return str(self.topic)
In the chat session model, I want to limit the options for counsellor field to the available_counsellors list in slot model.
How can I do this??
I want the same to reflect in my admin view also.
You need to update the queryset on the counsellor form field.
Assuming you are using a ModelForm, you can do this in your view:
chat_session = ChatSession.objects.get(pk=1)
form = ChatSessionForm(instance=chat_session)
form.fields["counsellor"].queryset = chat_session.slot.available_counsellors
You can alternatively do it in your ModelForm:
class ChatSessionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super (ChatSessionForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.slot:
options = self.instance.slot.available_counsellors
else
options = User.objects.none()
self.fields['counsellor'].queryset = options
For Django admin, something like this should work:
class ChatSessionAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
if kwargs['obj'].slot:
options = kwargs['obj'].slot.available_counsellors
else:
options = User.objects.none()
context['adminform'].form.fields['counsellor'].queryset = options
return super(ChatSessionAdmin, self).render_change_form(request, context, args, kwargs)

Modifying save() in django model keeps looping

I'm trying to create a model based on the Google Map API.
If the object does not exists, I want to save the name, address, longitude, latitude and google place ID. Below is my code: However, when I run it, it goes into a loop and does stop checking Google Map. Can you tell me what is wrong?
class Place(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
address = models.CharField(max_length=200, null=True, blank=True)
logitude = models.CharField(max_length=20, null=True, blank=True)
latitude = models.CharField(max_length=20, null=True, blank=True)
id_google = models.CharField(max_length=50, null=True, blank=True)
date_created = models.DateTimeField(_('date created'), default=timezone.now)
date_modified = models.DateTimeField(_('date_modified'), auto_now=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
try:
place = Place.objects.get(name=self.name)
except Place.DoesNotExist:
gmaps = googlemaps.Client(key=settings.GOOGLE_MAPS_API_KEY)
geocode_result = gmaps.geocode(self.address)
place = Place(name=self.name,
address=geocode_result[0]['formatted_address'],
logitude=geocode_result[0]['geometry']['location']['lng'],
latitude=geocode_result[0]['geometry']['location']['lat'],
id_google=geocode_result[0]['place_id'],
)
place.save()
return place
You call place.save() in the save(..) method, and thus triggering another save. You probably can just edit the item inplace, and then save it by using a super().save() call:
class Place(models.Model):
# ...
def save(self, *args, **kwargs):
try:
place = Place.objects.get(name=self.name)
except Place.DoesNotExist:
gmaps = googlemaps.Client(key=settings.GOOGLE_MAPS_API_KEY)
geocode_result = gmaps.geocode(self.address)[0]
self.address = geocode_result['formatted_address']
location = geocode_result['geometry']['location']
self.logitude = location['lng']
self.latitude = location['lat']
self.id_google = geocode_result['place_id']
super().save(*args, **kwargs)

How can i get a object from a other function in django

I have two functions projectTimerStart to start the timer and projectTimerStop
i want to use the object which is created in projectTimerStart and i want to end the time when projectTimerStop , and this should be saved in a database
ps: Both the functions are not in class they are normal functions
def projectTimerStart(request, slug):
project_detail = Project.objects.get(slug=slug)
b = ProjectTimer(time_started=datetime.now(),
working_project=project_detail,
working_freelancer=request.user
)
b.save()
return HttpResponseRedirect(reverse('project_timer', kwargs=
{"slug":slug}))
def projectTimerStop(request, slug):
project_detail = Project.objects.get(slug=slug)
#i want something here super method or something
return HttpResponseRedirect(reverse('project_timer', kwargs=
{"slug": slug}))
models.py
class Project(models.Model):
project_title = models.CharField(max_length=100)
project_description = models.TextField()
created_by = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='created')
assigned_to = models.ManyToManyField(
User, blank=True, related_name='assigned_by')
slug = models.SlugField()
hourly_budget = models.PositiveIntegerField(blank=True, null=True)
technologies = models.ManyToManyField(
Technologies, related_name='technologies_used')
time_posted = models.DateTimeField(auto_now_add=True)
request_id = models.ManyToManyField(
User, related_name='requested_by', blank=True)
def __str__(self):
return self.project_title
def save(self, *args, **kwargs):
self.slug = slugify(self.project_title)
super(Project, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('project_detail', kwargs={'slug': self.slug})
def get_timer_url(self):
return reverse('project_timer', kwargs={'slug': self.slug})
def round_datetime(dt):
minutes = round((dt.minute + float(dt.second) / 60) / 15) * 15 -
dt.minute
return dt + datetime.timedelta(minutes=minutes, seconds=-dt.second)
class ProjectTimer(models.Model):
time_started = models.DateTimeField()
time_ended = models.DateTimeField(blank=True, null=True)
working_project = models.ForeignKey(Project, on_delete=models.CASCADE)
working_freelancer = models.ForeignKey(
User, on_delete=models.CASCADE, blank=True, null=True)
If each of your project objects will have one and only project timer objects, you can add project_timer = models.OneToOneField(ProjectTimer) to your Project model and access to the project timer by using project_detail.project_timer.
If not, you need to know at least one feature of that project_timer in order to fetch it from database. Or you can iterate all of your ProjectTimer objects that belongs to that Project and select the appropriate one by:
models.py
class Project(models.Model):
# Some fields
project_timers = models.ManyToManyField(ProjectTimer)
views.py
def projectTimerStop(request, slug):
project_detail = Project.objects.get(slug=slug)
for pt in project_detail.project_timers.all():
if pt.some_field == "THIS IS CORRECT TIMER":
# Here is your project_detail
print(str(pt))
return HttpResponseRedirect(reverse('project_timer', kwargs=
{"slug": slug}))