Creating a Path Based on Multiple SlugFields in Django - python

I have a Django project based around creating tournaments and nesting specific objects within them. For instance, every tournament has multiple committees. When someone creates a tournament, I allow them to create a link with a SlugField. My code (so far) is as follows:
models.py
from django.db import models
from django.utils.text import slugify
class Tournament(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50, unique=True)
def _get_unique_slug(self):
'''
In this method a unique slug is created
'''
slug = slugify(self.name)
unique_slug = slug
num = 1
while Tournament.objects.filter(slug=unique_slug).exists():
unique_slug = '{}-{}'.format(slug, num)
num += 1
return unique_slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self._get_unique_slug()
super().save(*args, **kwargs)
class Committee(models.Model):
name = models.CharField(max_length=100)
belongsTo = models.ForeignKey(Tournament, blank=True, null=True)
slug = models.SlugField(max_length=50, unique=True)
def _get_unique_slug(self):
'''
In this method a unique slug is created
'''
slug = slugify(self.name)
unique_slug = slug
num = 1
while Committee.objects.filter(slug=unique_slug).exists():
unique_slug = '{}-{}'.format(slug, num)
num += 1
return unique_slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self._get_unique_slug()
super().save(*args, **kwargs)
views.py
from django.shortcuts import render, get_object_or_404
from .models import Tournament, Committee
def tournament_detail_view(request, slug):
tournament = get_object_or_404(Tournament, slug=slug)
return render(request, 'tournament/detail.html', {'tournament': tournament})
def committee_detail_view(request, slug):
committee = get_object_or_404(Committee, slug=slug)
return render(request, 'committee/detail.html', {'committee': committee})
urls.py
from . import views
from django.urls import path
app_name = 'tournament'
urlpatterns = [
path('<slug:slug>/', views.tournament_detail_view),
]
My question concerns urls.py. If a user creates a tournament called 'Zavala', they can currently access the website at example.com/zavala. However, if they create a committee named 'Cayde' under said tournament, there is no way for them to access the committee at example.com/zavala/cayde. The issue is that both of the sub-urls are slugs, and I'm not sure if Django can work with this. Is there a way to create a path that allows the user to go to the committee? I thought something along the lines of creating a function to test whether or not the tournament existed, but wasn't sure exactly how. Any tips? All I need is a working solution.

I'm not sure why you think you can't have two slugs. You can:
urlpatterns = [
path('<slug:slug>/', views.tournament_detail_view),
path('<slug:tournament_slug>/<slug:committee_slug>/', views. committee_detail_view),
]
and now your committee_detail_view becomes:
def committee_detail_view(request, tournament_slug, committee_slug):
committee = get_object_or_404(Committee, slug=committee_slug, belongsTo__slug=tournament_slug)
return render(request, 'committee/detail.html', {'committee': committee})

Related

Django API split data by unique ID

I am making Django API.
I collected place and review data using crwaling program.
I want to split review data by building name(unique key), but it seems all reviews are saved together and is spread to all URL.
models.py
from django.db import models
import uuid
# Create your models here.
from django.utils.text import slugify
def generate_unique_slug(klass, field):
origin_slug = slugify(field, allow_unicode=True)
unique_slug = origin_slug
numb = 1
while klass.objects.filter(slug=unique_slug).exists():
unique_slug = '%s-%d' % (origin_slug, numb)
numb += 1
return unique_slug
class BuildingData(models.Model):
building_name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=50, unique=True, allow_unicode=True, default=uuid.uuid1)
building_loc = models.CharField(max_length=50)
building_call = models.CharField(max_length=20)
#building_time = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if self.slug: # edit
if slugify(self.building_name, allow_unicode=True) != self.slug:
self.slug = generate_unique_slug(BuildingData, self.building_name)
else: # create
self.slug = generate_unique_slug(BuildingData, self.building_name)
super(BuildingData, self).save(*args, **kwargs)
class ReviewData(models.Model):
building_name = models.CharField(max_length=50)
review_content = models.TextField()
star_num = models.FloatField()
urls.py
from django.contrib import admin
from django.urls import path
from crawling_data.views import ReviewListAPI
from crawling_data.views import BuildingInfoAPI
urlpatterns = [
path('admin/', admin.site.urls),
path('api/buildingdata/', BuildingInfoAPI.as_view()),
path('api/buildingdata/<str:slug>/', ReviewListAPI.as_view())
]
models.py
# Create your views here.
from django.shortcuts import render
from rest_framework.response import Response
from .models import ReviewData
from .models import BuildingData
from rest_framework.views import APIView
from rest_framework import generics
from rest_framework import permissions
from .serializers import ReviewSerializer
from .serializers import BuildingSerializer
from django.shortcuts import render, get_object_or_404
class BuildingInfoAPI(APIView):
def get(self, request):
queryset = BuildingData.objects.all()
serializer = BuildingSerializer(queryset, many=True)
return Response(serializer.data)
class ReviewListAPI(APIView):
def get(self, request, slug):
queryset = ReviewData.objects.all()
serializer = ReviewSerializer(queryset, many=True)
return Response(serializer.data)
A part of crwaling program
if __name__=='__main__':
for item in building_dict:
BuildingData(building_name = item['place'], building_loc = item['location'], building_call = item['call']).save()
#BuildingData(building_name = item['place'], building_loc = item['location'], building_call = item['call'], building_time = item['time']).save()
for item in review_dict:
ReviewData(building_name = item['place'], review_content = item['review'], star_num = item['rate']).save()
Saving code above runs when program crawled all pages.
But this code saves all reviews on the same DB.
So what I want is this
URL : api/buildingdata/A/
A building - a review
A building - b review
URL : api/buildingdata/B/
B building - a review
B building - b review
But my API looks like this
URL : api/buildingdata/A/
A building - a review
A building - b review
B building - a review
B building - b review
URL : api/buildingdata/B/
A building - a review
A building - b review
B building - a review
B building - b review
Where should I fix to split review data by building name?
There looks to be two problems here. There is no explicit relationship defined between your two models and your ReviewListAPI View is accepting a slug, but doing nothing with it.
You should create a foreign key relationship between buildings and their reviews and then you can use a building's slug it to filter for reviews relevant only to that building:
You could update your ReviewData model like so:
class ReviewData(models.Model):
building = models.ForeignKey(BuildingData, on_delete=models.CASCADE)
...
class ReviewListAPI(APIView):
def get(self, request, slug):
queryset = ReviewData.objects.filter(building__slug=slug)
serializer = ReviewSerializer(queryset, many=True)
return Response(serializer.data)
Retrieving Objects
Foreign Keys
This may also necessetate that you update your serializers to reflect the relation between the objects:
If you dont want to make the Foreign Key relationship you could try this in your views:
class ReviewListAPI(APIView):
def get(self, request, slug):
building_name = BuildingData.objects.filter(slug=slug).values_list('building_name',flat=True)[0]
queryset = ReviewData.objects.filter(building_name=building_name)
serializer = ReviewSerializer(queryset, many=True)
return Response(serializer.data)
Here you filter the BuildingData by the slug, and return the single building name value against which you want to filter your ReviewData

filter queryset based on django countries field

this is my viewset:
class PollViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Poll.objects.all()
serializer_class = PollSerializer()
def get_queryset(self):
country = self.kwargs.get('pk', None)
if country is not None:
django_countries.Countries.name(country)
return self.queryset.filter(personality__country__name=country)
else:
country = self.request.user.preferred_country
return self.queryset.filter(personality__country=country)
model.py :
from django_countries.fields import CountryField
class Personality(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField()
age = models.IntegerField()
class Gender(models.TextChoices):
MALE = 'MALE', _('Male')
FEMALE = 'FEMALE', _('Female')
OTHER = 'OTHER', _('Other')
gender = models.CharField(
max_length=6,
choices=Gender.choices,
default=Gender.MALE,
)
country = CountryField(null=True)
picture = models.ImageField(upload_to='uploads/profile_images/%Y/%m/%d')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Poll(RulesModel):
personality = models.OneToOneField(Personality, related_name='polls', on_delete=models.CASCADE)
start_date = models.DateTimeField(null=True)
end_date = models.DateTimeField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return 'poll#' + str(self.id) + ' ' + self.personality.name
Urls.py
from django.conf.urls.static import static
from django.urls import path, include
from liderate import settings
from .views import PollViewSet, PersonalityViewSet, PollListViewSet
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'polls', PollViewSet)
router.register(r'polls-list', PollListViewSet)
router.register(r'personalities', PersonalityViewSet)
urlpatterns = [
path('', include(router.urls)),
]
here it raises exception that "Unsupported lookup 'name' for CountryField or join on the field not permitted."
this is raised in the if condition (else condition works perfectly fine). is there any other way of filtering the query set based on the country's name
i actually want to view only the polls related to country passed as a param in the get url
I had a field named location_countries in my project model based on CountryField. It was a multiselect and I needed to create a search based on country name or any other information available in project's other fields. conventional ways e.g "filter(fieldname__icontains) or Q(fieldname__country__name) etc failed. After a lot of trouble, search and thinking, I finally devised a workaround that is working as I intended. Now by giving any keywords for example "samo" in my search field it returns all projects based in "American Samoa" or "afghan" and it retruns all projects based in "Afghanistan". Hope This helps you also. Search field in normal html input tag named "searchTxt". to avoid duplication i first converted result to set and then back to list.
def viewProjects(request):
if request.user.is_authenticated:
if 'searchTxt' in request.GET:
qry = request.GET['searchTxt']
countries_list = []
pros = Project.objects.all()
for pro in pros:
for i in pro.location_countries:
if qry.lower() in i.name.lower():
countries_list.append(pro.project_heading)
res=Project.objects.filter(project_heading__in=countries_list)
projs = Project.objects.filter(
Q(project_id__icontains=qry)|
Q(project_heading__icontains=qry) |
Q(project_description__icontains=qry)|
Q(sla_type__icontains=qry)|
Q(project_type__icontains=qry)|
Q(project_priority__icontains=qry)|
Q(location_countries__exact=qry)|
Q(location_cities__icontains=qry)|
Q(project_manager__username__icontains=qry)|
Q(escalation_manager__username__icontains=qry)
)
project = list(chain(res, projs))
prj = set(project)
projects=list(prj)
context = { 'projects':projects }
return render(request, 'admin_view_projects.html', context)
else:
projects = Project.objects.all()
context = { 'projects':projects }
return render(request, 'admin_view_projects.html', context)
else:
return redirect('index')

NoReverse Match in Mezzanine - I am using get_absolute_url for slug field

I am currently developing a mezzanine project. I always get "NoReverseMatch":
Reverse for 'download' with arguments '()' and keyword arguments '{u'slug': u'read-metxt-1-2-3-4-5-6-7-8-9-10'}' not found. 0 pattern(s) tried: []
In my previous app before I used Mezzanine, there was no error. I just copy-pasted the code and modify it accordingly.
Sorry for asking the same question. I know there are other similar questions as mine, but none of them is working.
models.py
from __future__ import unicode_literals
from django.db import models
from django.core.urlresolvers import reverse
from django.db.models.signals import pre_save
from django.utils.text import slugify
import os
# Create your models here.
class ReportOutline_File(models.Model):
slug = models.SlugField(unique=True, max_length=100)
report_outline = models.FileField()
def get_absolute_url(self):
return reverse('txttoppt: download', kwargs={"slug": self.slug})
def create_slug(instance, new_slug=None):
slug = slugify(instance.report_outline.name) #slugifying the title
if new_slug is not None:
slug = new_slug
qs = ReportOutline_File.objects.filter(slug=slug).order_by("-id")
exists = qs.exists()
if exists:
new_slug = "%s-%s" %(slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
def pre_save_post_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = create_slug(instance)
print instance.slug
pre_save.connect(pre_save_post_receiver, sender=ReportOutline_File)
urls.py (main: mezzanine)
urlpatterns += [
url("^$", direct_to_template, {"template": "index.html"}, name="home"),
url("^convert-to-ppt/", include("toppt.urls", namespace="txttoppt")),]
urls.py (app)
from .views import (
converttexttoppt_func,
download
)
urlpatterns = [
url(r"^", converttexttoppt_func, name="converttexttoppt"),
url(r"^download/(?P<slug>[\w-]+)/$", download, name='download')]
views.py
def converttexttoppt_func(request):
form = ReportOutlineForm(request.POST or None, request.FILES or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(instance.get_absolute_url())
context = {
"form": form
}
return render(request, "texttoppt.html", context)
def download(request, slug):
instance = get_object_or_404(ReportOutline_File, slug=slug)
#more codes in between....
return render(request, "download.html", context)
Please help! Thank you. I've been looking for solution for this error for two days now! Tsk!
The slug you're providing includes numbers, but the url doesn't match numbers
u'read-metxt-1-2-3-4-5-6-7-8-9-10'
so you need to update your url accordingly
url(r"^download/(?P<slug>[\w\d-]+)/$", download, name='download')

getting the id from a foreignkey relationship django

I want to get the id or pk of a ForeignKey relationship post_comment but I've tried many different ways to catch it and i do not have any good result, please guys give me a hand in this situation
In views.py
class createComment(View):
form_class = CommentForm
template_name = "createComment.html"
def get(self, request):
form = self.form_class(None)
return render(request, self.template_name, {'form':form})
def post(self, request):
obj = self.form_class(None)
obj.title_comment = self.request.POST['title_comment']
obj.body_comment = self.request.POST['body_comment']
obj.post_comment = self.pk
obj.save()
In models.py
class Comment(models.Model):
user_comment = models.ForeignKey("auth.User")
title_comment = models.CharField(max_length=50)
body_comment = models.TextField()
timestamp_comment = models.DateTimeField(auto_now=True)
post_comment = models.ForeignKey("Post", null=True)
status_comment = models.BooleanField(default=True)
def __unicode__(self):
return unicode(self.title_comment)
def __str__(self):
return self.title_comment
You can pass a primary key in the url, and then use it in your class as one way.
kwargs.get(pk name)
You could change post to:
def post(self, request, *args, **kwargs)
You then can't just assign obj.post_comment = kwargs.get(pk) you have to actually get the object.
Post.objects.get(pk = pk)
You might want to also consider renaming fieldname_comment to just fieldname for your models fields. Seems a bit redundant to have _comment on every single field in the Comment model.
I don't know how works class based views but I can tell you that self.pk does not exist in class based view, you would try get form instance and get the I'd field from this instance...

django subcategory slug filter

Getting my head around Django and followed the tango with Django book, but this last issue gets me after adding subcategories, which are not included in that tutorial.
I have the following:
models.py
class Category(models.Model):
"""Category"""
name = models.CharField(max_length=50)
slug = models.SlugField()
def save(self, *args, **kwargs):
#self.slug = slugify(self.name)
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
class SubCategory(models.Model):
"""Sub Category"""
category = models.ForeignKey(Category)
name = models.CharField(max_length=50)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(SubCategory, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
and
urls.py
(r'^links/$', 'rango.views.links'),
(r'^links/(?P<category_name_slug>[\w\-]+)/$', 'rango.views.category'),
(r'^links/(?P<category_name_slug>[\w\-]+)/(?P<subcategory_name_slug>[\w\-]+)/$', 'rango.views.subcategory'),
and
views.py
#require_GET
def links(request):
"""Linkdirectory Page"""
category_list = Category.objects.order_by('name')
context_dict = {'categories': category_list}
return render(request, 'links.html', context_dict)
#require_GET
def category(request, category_name_slug):
"""Category Page"""
category = Category.objects.get(slug=category_name_slug)
subcategory_list = SubCategory.objects.filter(category=category)
context_dict = {'subcategories': subcategory_list}
return render(request, 'category.html', context_dict)
#require_GET
def subcategory(request, subcategory_name_slug, category_name_slug):
"""SubCategory Page"""
context_dict = {}
try:
subcategory = SubCategory.objects.get(slug=subcategory_name_slug)
context_dict['subcategory_name'] = subcategory.name
websites = Website.objects.filter(sub_categories=subcategory)
context_dict['websites'] = websites
context_dict['subcategory'] = subcategory
except SubCategory.DoesNotExist:
return render(request, 'subcategory.html', context_dict)
This all works nicely up to the point I add subcategories with the same name, e.g. the subcategory "other" for multiple categories.
I understand why, when I reach "def subcategory" my slug will return multiple subcategories so I need to limit these to the related category in some way, like a
"SELECT
subcategory = SubCategory.objects.get(slug=subcategory_name_slug)
WHERE
subcategory = SubCategory.objects.filter(category=subcategory)
CLAUSE"
or something ;)
Not sure what's the best route to take on this and how to filter these
Given that you may have two different SubCategory objects with the same name for two different Category objects, as you suggested you can add the Category as an additional filter.
Filter by both SubCategory.slug and Category.slug
To achieve this I see you have a view that takes slugs for both SubCategory and Category, that you defined like this subcategory(request, subcategory_name_slug, category_name_slug). Those are sufficient to filter:
subcategory = SubCategory.objects.get(
slug=subcategory_name_slug,
category__slug=category_name_slug
)
^
|__ # This "double" underscore category__slug is a way to filter
# a related object (SubCategory.category)
# So effectively it's like filtering for SubCategory objects where
# SubCategory.category.slug is category_name_slug
You see above I've used SubCateogry.objects.get(...) to get a single object instead of `SubCategory.objects.filter(...) which can return many objects.
Enforcing unicity of SubCategory.name per Category
To do this safely with get(), there needs to be a guarantee that for any given Category, there will no more than one Subcategory with the same name
You can enforce this condition with unique_together
class SubCategory(models.Model):
class Meta:
unique_together = (
('category', 'name'), # since slug is based on name,
# we are sure slug will be unique too
)

Categories