Difficulty adding slug to Generic Detail View in Django - django

The view works with just the PK, however, the problem is that my PK on the live site is an incoherent string and I want to make it coherent titling for an article to boost SEO. I don't want to change the PK to the slug. I want both.
When I try to add both the PK and a slug it fails and I get the error: no reverse match.
URL path:
path('idea/<slug:slug>,<int:pk>', views.IdeaDetailView.as_view(), name='detail')
Model:
class Idea(models.Model):
idea_id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Searches ID')
idea_number = models.IntegerField(blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
idea_title = models.CharField(max_length=300, blank=True, null=True)
idea_text = NonStrippingTextField(max_length=10000, blank=True, null=True)
Views.py:
class IdeaDetailView(generic.DetailView):
model = Idea
template_name = "idea/detail.html"
def get_context_data(self, **kwargs):
context = super(IdeaDetailView, self).get_context_data(**kwargs)
context['results'] = Idea.objects.filter(idea_id=self.kwargs.get('pk'))
return context
Admin.py:
class IdeaAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("idea_title",)}
I want to add a slug which is the idea_title, however if I try to add that into the URL it fails.

Try adding a slugfield, get_absolute_url method, and save method to your Idea model, like this:
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
class Idea(models.Model):
idea_id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Searches ID')
idea_number = models.IntegerField(blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
idea_title = models.CharField(max_length=300, blank=True, null=True)
idea_text = NonStrippingTextField(max_length=10000, blank=True, null=True)
slug = models.SlugField(
default='',
editable=False,
max_length=300,
)
def get_absolute_url(self):
kwargs = {
'pk': self.id,
'slug': self.slug
}
return reverse('idea-pk-slug-detail', kwargs=kwargs)
def save(self, *args, **kwargs):
value = self.title
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
Then, in urls.py:
path('idea/<int:pk>-<str:slug>/', views.IdeaDetailView.as_view(), name='detail')
And, in views.py right under template_name:
query_pk_and_slug = True
Two more methods and more info found HERE.
Hope this helps!

Related

Django foreignkey between two databases

I'm looking to connect two SQLite tables together (oh dear).
I found this solution : How to use django models with foreign keys in different DBs? , I adapted it to my models and my code (I think). I have no bad answers from django. However, I would like to modify the entry with the admin view of django, and when I try to add the entry with the foreign key of another database, I get this answer:
Exception Type: OperationalError at /admin/users/character/add/
Exception Value: no such table: books
How to adapt to fix it?
models database default
from django.db import models
from users.related import SpanningForeignKey
from books.models import Books
class Character(models.Model):
last_name = models.fields.CharField(max_length=100)
first_name = models.fields.CharField(max_length=100)
book = SpanningForeignKey('books.Books', null=True, on_delete=models.SET_NULL)
def __str__(self):
return f'{self.first_name} {self.last_name}'
models external database
from django.db import models
# Create your models here.
class Books(models.Model):
title = models.TextField()
sort = models.TextField(blank=True, null=True)
timestamp = models.TextField(blank=True, null=True) # This field type is a guess.
pubdate = models.TextField(blank=True, null=True) # This field type is a guess.
series_index = models.FloatField()
author_sort = models.TextField(blank=True, null=True)
isbn = models.TextField(blank=True, null=True)
lccn = models.TextField(blank=True, null=True)
path = models.TextField()
flags = models.IntegerField()
uuid = models.TextField(blank=True, null=True)
has_cover = models.BooleanField(blank=True, null=True)
last_modified = models.TextField() # This field type is a guess.
class Meta:
managed = False
db_table = 'books'
app_label = 'books'
class Languages(models.Model):
lang_code = models.TextField()
class Meta:
managed = False
db_table = 'languages'
app_label = 'books'
class BooksLanguagesLink(models.Model):
book = models.ForeignKey(Books, null=True, on_delete=models.SET_NULL, db_column='book')
lang_code = models.ForeignKey(Languages, null=True, on_delete=models.SET_NULL, db_column='lang_code')
item_order = models.IntegerField()
class Meta:
managed = False
db_table = 'books_languages_link'
app_label = 'books'
admin books
from django.contrib import admin
from books.models import Books, Languages
class BooksModelAdmin(admin.ModelAdmin):
using = 'calibre_db'
def save_model(self, request, obj, form, change):
obj.save(using=self.using)
def delete_model(self, request, obj):
obj.delete(using=self.using)
def get_queryset(self, request):
return super().get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
return super().formfield_for_foreignkey(db_field, request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request, **kwargs):
return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
# Register your models here.
admin.site.register(Books, BooksModelAdmin)

how to make a char field behave like URLField in django

I am using Django and I want a field for URLs I know there is a URLField in Django but I want that field to be a charfield and I don't want users to submit anything other than URL in that charfield
take a look at my models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
email = models.EmailField(max_length=500, blank=True, null=True)
social_github = models.CharField(max_length=200, blank=True, null=True)
social_twitter = models.CharField(max_length=200, blank=True, null=True)
social_linkedin = models.CharField(max_length=200, blank=True, null=True)
social_youtube = models.CharField(max_length=200, blank=True, null=True)
social_website = models.CharField(max_length=200, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
id = models.UUIDField(
default=uuid.uuid4, unique=True, primary_key=True, editable=False)
def __str__(self):
return str(self.username)
Please answer how can I make a char field behave like a URL field
An URLField is in essence just a CharField with some validation (and a default name that defaults to (a translation of) URL and a maximum length of 200. Indeed if we look at the source code [GitHub], we see:
class URLField(CharField):
default_validators = [validators.URLValidator()]
description = _("URL")
def __init__(self, verbose_name=None, name=None, **kwargs):
kwargs.setdefault('max_length', 200)
super().__init__(verbose_name, name, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if kwargs.get("max_length") == 200:
del kwargs['max_length']
return name, path, args, kwargs
def formfield(self, **kwargs):
# As with CharField, this will cause URL validation to be performed
# twice.
return super().formfield(**{
'form_class': forms.URLField,
**kwargs,
})
It also uses a different form field: the URLField is [implemented [GitHub] as):
class URLField(CharField):
widget = URLInput
# …
It will thus use a URLInput widget, but there is nothing that prevents you from using another widget. Indeed, we can implement the model with:
class Profile(models.Model):
# …
social_github = models.URLField(blank=True, null=True)
social_twitter = models.URLField(blank=True, null=True)
social_linkedin = models.URLField(blank=True, null=True)
social_youtube = models.URLField(blank=True, null=True)
social_website = models.URLField(blank=True, null=True)
# …
If you want to change the widget, for example in a ModelForm, you can work with:
class MyProfileForm(forms.ModelForm):
# …
class Meta:
# …
widgets = {
'social_github': forms.TextInput,
'social_twitter': forms.TextInput,
'social_linkedin': forms.TextInput,
'social_youtube': forms.TextInput,
'social_website': forms.TextInput
}

How to create model allowing infinite submodels in django?

Im creating a site where you can write down your goals, you should be able to split every goal into subgoals if chosen, and allow those subgoals to be split into subgoals infinitely.
This code below shows what i came up with first for the models, the first model is for creating a goal, the second model can either either be a subgoal of the goal or a subgoal of the subgoal.
But it seems like a really bad way to go around this problem.
Django semi-newbie BTW...
from django.db import models
from django.contrib.auth.models import User
class Goal(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
title = models.CharField(max_length=70, null=True)
content = models.TextField(blank=True)
slug = models.SlugField(max_length=50, editable=False)
date_created = models.DateTimeField(auto_now_add=True, null=True)
class Meta:
unique_together = ['user', 'title']
def __str__(self):
return self.user.username + " - " + self.title
def save(self, *args, **kwargs):
self.slug = self.title.replace(' ', '-').lower()
super(Goal, self).save(*args, **kwargs)
class SubGoal(models.Model):
goal = models.ForeignKey(
Goal, on_delete=models.CASCADE, null=True, blank=True)
parent = models.ForeignKey(
"SubGoal", on_delete=models.CASCADE, null=True, blank=True)
title = models.CharField(max_length=70)
content = models.TextField(blank=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
try:
return self.goal.title + " - " + self.title
except:
return self.parent.title + " - " + self.title
You can make a ForeignKey to self. If the ForeignKey is NULL, then that goal has no parent, otherwise it refers to the parent:
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
django.utils.text import slugify
class Goal(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=70)
content = models.TextField(blank=True)
slug = models.SlugField(max_length=50, editable=False)
date_created = models.DateTimeField(auto_now_add=True)
parent = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
default=None,
related_name='subgoals'
)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'title'], name='user_title')
]
def __str__(self):
if self.parent_id is None:
return '{}-{}'.format(self.user.username, self.title)
else:
return '{}-{}'.format(str(self.parent), self.title)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Goal, self).save(*args, **kwargs)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

"Invalid field name(s) given in select_related" when searching one-to-many relationship

my apologies as I am new to Django and this seems like a "Django 101" type problem, but I just can't seem to get it to work. I have a Django model for "Services" which has a related model for "Keywords" via a one-to-many relationship. I'm simply trying to return the related keywords when I query the services, but when I run the view, I keep getting the error:
Invalid field name(s) given in select_related: 'keyword'. Choices are: (none)
My models are as follows:
from uuid import uuid4
from django.db import models
from django.utils.text import slugify
from blog.models import Tag, Category
class Service(models.Model):
id_service = models.UUIDField(primary_key=True, default=uuid4, editable=False)
created_ts = models.DateTimeField(auto_now_add=True)
updated_ts = models.DateTimeField(auto_now=True)
service_name = models.CharField(
db_index=True, max_length=50, blank=False, null=False
)
slug = models.CharField(max_length=50, unique=True)
font_awesome_icon = models.CharField(blank=True, null=True, max_length=30)
service_image = models.ImageField(
blank=True, null=True, upload_to="images/services", max_length=None
)
service_page_image = models.ImageField(
blank=True, null=True, upload_to="images/services", max_length=None
)
service_description = models.TextField(blank=False, null=False)
service_description_brief = models.CharField(
max_length=200, blank=False, null=False
)
rank = models.IntegerField()
def save(self, *args, **kwargs):
self.slug = slugify(self.service_name)
super(Service, self).save(*args, **kwargs)
def __str__(self):
return self.service_name
class ServiceKeyword(models.Model):
id_servicekeywords = models.UUIDField(
primary_key=True, default=uuid4, editable=False
)
created_ts = models.DateTimeField(auto_now_add=True)
updated_ts = models.DateTimeField(auto_now=True)
keyword = models.CharField(max_length=60, blank=False, null=False)
service = models.ForeignKey(Service, on_delete=models.CASCADE)
def __str__(self):
return self.keyword
And the view that is throwing the error is:
import random
import markdown2
import geoip2.database
import datetime
from django.views.generic.base import TemplateView
from django.views.generic.list import ListView
from django.core.paginator import Paginator
from django.http import Http404
from django.shortcuts import render
from django.urls import reverse
from services.models import Service, ServiceKeyword
class ServiceView(TemplateView):
template_name = "services/service.jinja"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
servicename = self.kwargs["servicename"]
service_list = Service.objects.select_related("keyword").order_by("rank")
context["service_list"] = service_list
context["faq_list"] = Post.objects.filter(categories__slug=servicename)
for faq in context["faq_list"]:
faq.content = markdown2.markdown(faq.content)
# Get service with that slug and take from list
service = service_list.filter(slug=servicename)[0]
context["keyword_list"] = service.keyword.all().order_by("?")[7]
context["service"] = service
)
return context
Any help from the pros would be greatly appreciated, as I've looked at the docs and spend an inordinate amount of time trying to fix. Thank you!
Defining a related_name on your field definition should solve your problem :
class ServiceKeyword(models.Model):
...
service = models.ForeignKey(Service, related_name='keyword', on_delete=models.CASCADE)
...
You can find the documentation for the related_name HERE

django url issue using mutiple regular expressions

I am having an issue with an url and regular expression I get the error
AttributeError: Generic detail view EmployeeDetailView must be called with either an object pk or a slug.
What I am to achieve is to get to a user detail page coming from a specific project
url(r'^project/(?P<pk>[0-9]+)/$',views.ProjectDetailView.as_view(), name='ProjectDetails'),
url(r'^project/(?P<pk1>[0-9]+)/(?P<pk2>[0-9]+)/$',views.EmployeeDetailView.as_view(), name='EmployeDetails'),
my view is :
Project detail :
class ProjectDetailView(generic.DetailView, LoginRequiredMixin):
#import pdb; pdb.set_trace()
model = Project
template_name = 'project_details.html'
def get_context_data(self, **kwargs):
context = super(ProjectDetailView, self).get_context_data(**kwargs)
try:
team_name = Project.objects.get(id=self.kwargs['pk']).team_id.members.all()
context['team_name'] = team_name
except AttributeError:
pass
return context
class EmployeeDetailView(generic.DetailView, LoginRequiredMixin):
#import pdb; pdb.set_trace()
model = MyUser
template_name = 'Employee_Details.html'
def get_context_data(self, **kwargs):
context = super(EmployeeDetailView, self).get_context_data(**kwargs)
employee_name = MyUser.objects.get(id=self.kwargs['pk'])
context['employee_name'] = employee_name
return context
HTML link :
<span class="fa fa-id-card-o" aria-hidden="true"> Show Results
models:
MyUser models:
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
first_name = models.CharField(max_length=150, blank=True, null=True)
last_name = models.CharField(max_length=150, blank=True, null=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_hr = models.BooleanField(default=False)
is_candidate = models.BooleanField(default=False)
is_employee = models.BooleanField(default=False)
company = models.CharField(max_length=100, blank=True, null=True)
Project model:
class Project(models.Model):
name = models.CharField(max_length=250)
team_id = models.ForeignKey(Team, blank=True, null=True)
project_hr_admin = models.ForeignKey('registration.MyUser', blank=True, null=True)
candidat_answers = models.ManyToManyField('survey.response')
Team models:
class Team(models.Model):
team_name = models.CharField(max_length=100, default = '')
team_hr_admin = models.ForeignKey(MyUser, blank=True, null=True)
members = models.ManyToManyField(MyUser, related_name="members")
could you please help me to figure it ? thx you ;)
Django doesn't know how to use pk1 and pk2 to fetch the object for the view. I would override the get_object method and fetch the object there.
from django.shortcuts import get_object_or_404
# Note mixin should come first
class EmployeeDetailView(LoginRequiredMixin, generic.DetailView):
model = MyUser
template_name = 'Employee_Details.html'
def get_object(self, queryset=None):
return get_object_or_404(MyUser, pk=self.kwargs['pk2'], project=self.kwargs['pk1'])
...
Another option is to set pk_url_kwarg = 'pk2'. This tells Django that pk2 is the primary key of the MyUser object, so there is no need to override get_object. However if you do this, then Django will ignore the pk1 from the URL.
class EmployeeDetailView(generic.DetailView, LoginRequiredMixin):
#import pdb; pdb.set_trace()
model = MyUser
pk_url_kwarg = 'pk2'
template_name = 'Employee_Details.html'