Django API split data by unique ID - python

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

Related

Getting an empty list when using filter - Django REST

I have my API in Django REST Framework:
Here is my models.py:
class myModel(models.Model):
user_email = models.CharField(max_length= 200, null= False)
Here is my views.py:
class GetItemsByEmail(generics.ListAPIView):
def get_queryset(self):
email_items = self.request.query_params.get("user_email")
if(email_items is not None):
itemsReturned = myModel.objects.all().filter(user_email = email_items)
return Response(data= itemsReturned)
Here is my urls.py:
url_patterns = [
path('users/account=<str:id>/shipments', GetItemsByEmail.as_view()),
]
My Question:
I am getting an empty list, getting nothing from making an API call to the above endpoint.
I want to get all the items in the database associated with a particular email?
In your views.py:
from rest_framework import generics
from .models import * # noqa
from .serializers import *
class GetItemsByEmail(generics.ListAPIView):
queryset = MyModel.objects.all() # noqa
serializer_class = MyModelSerializer
def get_queryset(self):
if self.kwargs.get('user_email_pk'):
return self.queryset.filter(id=self.kwargs.get('user_email_pk'))
return self.queryset.all()
In models.py I had to create another model to have the result that you want (get all database by a specific user_email!):
from django.db import models
class MyModel(models.Model):
user_email = models.CharField(max_length=200, null=False)
def __str__(self):
return self.user_email
class ServicesModel(models.Model):
# Just an example to emulate the expected result, do not worry about it!
name = models.CharField('Name', max_length=200)
user_email_service = models.ForeignKey(MyModel, related_name='services', on_delete=models.CASCADE) # Just an example to emulate the expected result, do not worry about it!
def __str__(self):
return self.name
In serializers.py:
from rest_framework import serializers
from .models import MyModel, ServicesModel
class ServiceModelSerializer(serializers.ModelSerializer):
class Meta:
model = ServicesModel
fields = (
'name',
)
class MyModelSerializer(serializers.ModelSerializer):
services = ServiceModelSerializer(many=True, read_only=True)
class Meta:
model = MyModel
fields = (
'id',
'user_email',
'services',
)
In urls.py:
from django.urls import path
from core.views import GetItemsByEmail
urlpatterns = [
path('users/', GetItemsByEmail.as_view(), name='users'), # Ignore!
path('users/account=<str:user_email_pk>/shipments/', GetItemsByEmail.as_view(), name='user_email'),
]
In the test that I made localy I created two 'user_email' and each one have diferent 'services' so you are able to get all the data by the id, images of the result:
You obviously only need to get attention in 'views.py' and 'serializers.py', I just created all this code to get in the expected result!
If you want your query to be case insensitive, you can try the following:
myModel.objects.filter(user_email__iexact=email_items)

How to Query model filed elements in the same URL

I am writing a single model application in DRF. My model looks like this:
class Superhero(models.Model):
squad_name = models.CharField(max_length=100)
hometown = models.CharField(max_length=30)
formed = models.DateField()
active = models.BooleanField()
members = JSONField()
My viewset looks like this:
class SuperheroViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving superheros.
"""
serializer_class = SuperheroSerializer
def list(self, request):
"""list superhero object"""
queryset = Superhero.objects.filter()
serializer = SuperheroSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Superhero.objects.filter()
superhero = get_object_or_404(queryset, pk=pk)
serializer = SuperheroSerializer(superhero)
return Response(serializer.data)
and finally, my router is:
router = DefaultRouter()
router.register(r'superhero', SuperheroViewSet, basename='superhero')
urlpatterns = router.urls
Now how do I set a URL,so I would query the members field like:
//superhero/{id}/members to get specific id members. I tried drf nested URL but didn't work. The url I have works for superhero/ and superhero/{id}.
You should use detailed viewset action.
Your code would looks smth like this:
from rest_framework.decorators import action
from rest_framework.shortcuts import get_object_or_404
from rest_framework.response import Response
class SuperheroViewSet():
...
#action(detail=True, methods=['get'], url_path='members')
def get_superhero_members(self, request, pk=None):
superhero = get_object_or_404(self.get_queryset(), pk=pk)
members = <get members of your hero>
return Response(members)
You should also probably use custom serializer for members and in response return: return Response(CustomSerializer(members).data)

Get detail by Unique ID but not PK in Django Rest Framework URLs

I am trying to create Rest API using DRF. Wanted to get detail by using UniqueId. I can use PK and get the output but wanna use unique id (token_id in my jobs Model) created in the model field.
Models.py
from django.db import models
from rest_api.util import unique_slug_generator
from django.urls import reverse
# Create your models here.
class Jobs(models.Model):
token_id = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=100)
url = models.URLField()
environment = models.CharField(max_length=100, null=True)
runtestnow = models.BooleanField(default=False)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('token_id', kwargs={'token_id':self.token_id})
class Queue(models.Model):
tokenid = models.ForeignKey(Jobs, on_delete=models.CASCADE)
date = models.DateField(auto_now=True)
def __str__(self):
return self.tokenid
class VM(models.Model):
vm_count = models.IntegerField(default=120)
def __str__(self):
return f"VM Count: {self.vm_count}"
Urls.py
from django.urls import path, include
from . import views
from .views import (RegisterTestMethodView,
RegisterTestMethodViewDetail,
CheckStatusView,
ReleaseTokenView
)
from rest_framework import routers
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
router = routers.DefaultRouter()
router.register('jobs', views.JobsView)
urlpatterns = [
path('', include(router.urls)),
path('registertestmethod/', RegisterTestMethodView.as_view()),
path('registertestmethod/<int:pk>/',
RegisterTestMethodViewDetail.as_view()),
path('checkstatus/<int:pk>', CheckStatusView.as_view()),
path('checkstatus/<token_id>', CheckStatusView.as_view()),
path('releasetoken/<int:pk>', ReleaseTokenView.as_view()),
]
Serializers.py
from rest_framework import serializers
from .models import Jobs
from django.utils.crypto import get_random_string
class JobsSerializers(serializers.HyperlinkedModelSerializer):
token_id = serializers.CharField(default=get_random_string(length=25))
class Meta:
model = Jobs
fields = ('id', 'name', 'url','runtestnow','token_id')
class CheckStatusSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobs
fields = ('id','runtestnow')
class RegisterTestMethodSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobs
fields = ('id', 'name', 'url', 'environment', 'runtestnow', 'token_id')
Views.py
from rest_framework import viewsets, permissions, authentication
from .models import Jobs, VM, Queue
from .serializers import (JobsSerializers,
RegisterTestMethodSerializers,
CheckStatusSerializers)
import json
import datetime
import collections
collections.deque()
#3rd time
from rest_framework import generics
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authentication import (SessionAuthentication,
BasicAuthentication,
TokenAuthentication)
from rest_framework.permissions import IsAuthenticated
from django.utils.crypto import get_random_string
with open('greet/static/greet/static/config.json', 'r') as
data_config:
data_ready = json.load(data_config)
totalVM = int(data_ready['totalVM'])
max_vm = int(data_ready['max_vm_count'])
grid_name = (data_ready['GridNameForDev'])
min_vm = int(data_ready['min_vm_count'])
class RegisterTestMethodView(APIView):
# authentication_classes = [SessionAuthentication,
TokenAuthentication, BasicAuthentication]
# permission_classes = [IsAuthenticated] # No access (not even
read if not authorized)
def get(self, request):
snippets = Jobs.objects.all()
serializer = RegisterTestMethodSerializers(snippets,
many=True)
return Response(serializer.data)
def post(self, request):
queue = VM.objects.all()
id_token = get_random_string(length=25)
if not queue:
queue = VM(vm_count=totalVM)
queue.save()
else:
for queue_obj in queue:
queue = queue_obj
if queue.vm_count > min_vm:
queue.vm_count -= max_vm
queue.save()
request.data["token_id"] = id_token
request.data["runtestnow"] = True
else:
request.data["token_id"] = id_token
request.data["runtestnow"] = False
serializer = RegisterTestMethodSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'TokenId': serializer.data['token_id'],
'RunTestNow': serializer.data['runtestnow'],
'VmCount': queue.vm_count,
'GridName': grid_name, 'Vm_left':
queue.vm_count}, status=status.HTTP_201_CREATED)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
class JobsView(viewsets.ModelViewSet):
queryset = Jobs.objects.all()
serializer_class = JobsSerializers
lookup_field = 'token_id'
class CheckStatusView(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk, token_id):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def get(self, request, token_id):
pk = request.GET.get('pk')
print(pk)
queue = VM.objects.get()
job_list = Jobs.objects.exclude(runtestnow=True)
filtered = Jobs.objects.filter(id=pk)
next_q = job_list.order_by('id').first()
waitlist = 1
return Response(
{"tokenid": token_id, "Runtestnow": False, "VMcount":
queue.vm_count,
'GridName': grid_name, 'waitlist #': waitlist,
'Vm_left':
queue.vm_count}, status=status.HTTP_201_CREATED)
def post(self, request, pk):
queue = VM.objects.get()
vm_count = queue.vm_count
job_list = Jobs.objects.exclude(runtestnow=True)
filtered = Jobs.objects.filter(id=pk)
next_q = job_list.order_by('id').first()
waitlist = int(pk-next_q.id + 1)
if next_q:
print(next_q.id)
if next_q.id == pk and queue.vm_count > min_vm:
queue.vm_count -= max_vm
filtered.update(runtestnow=True)
queue.save()
vm_used = max_vm
else:
filtered.update(runtestnow=False)
queue.save()
vm_used = 0
snippet = self.get_object(pk)
serializer = RegisterTestMethodSerializers(snippet)
return Response({"tokenid": serializer.data["id"],
"Runtestnow": serializer.data['runtestnow'], "VMcount":
vm_used,
'GridName': grid_name, 'waitlist #': waitlist ,
'Vm_left': queue.vm_count},
status=status.HTTP_201_CREATED)
class ReleaseTokenView(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def delete(self, request, pk, format=None):
queue = VM.objects.get()
if not queue:
queue = VM(vm_count=totalVM)
if not self.get_object(pk):
print("Not Method Called...")
return
if queue.vm_count < totalVM :
queue.vm_count += max_vm
queue.save()
elif queue.vm_count + max_vm > totalVM:
queue.vm_count = totalVM
queue.save()
snippet = self.get_object(pk)
snippet.delete()
return Response(data={'Released': True},
status=status.HTTP_204_NO_CONTENT)
I can get information using but I wanna user token_id. I can do that using Serializers as it is in jobs.
If I do
localhost/jobs/xJcn8XxF2g9DmmwQwGS0Em754. # --> I get the output but I
# wanna use and I am aware
#that this will return all CRUD methods but how do I apply the
#business logic in Serializers.
localhost/checkstatus/xJcn8XxF2g9DmmwQwGS0Em754 . # --> I wanna
# apply business logic before getting the output. Which
# returns Response related to the PK as well.
What is the best way to do it?
Do I add it on serializer.py(how) or views.py?
I would appreciate it if you provide any helpful documents.
You should set lookup_field as token_id in your serializer and viewset.
Here is answer Django Rest Framework: Access item detail by slug instead of ID
Actually I was able to do it by some research. It seems like I have to pass a unique id (token_id) in URL and query using the same unique id (token_id) on the views.py. I was aware that there is modelviewset that does it effortlessly as mentioned by Ishak, but I wanted to use APIView and on top of that I wanted some business logic to be added. I probably have to do some more research on how to add logic to modelviewset. Here is my Solution.
Views.py
def get(self, request, token_id):
get_job = Jobs.objects.get(token_id=token_id)
pk = get_job.id
job_list = Jobs.objects.exclude(runtestnow=True)
next_q = job_list.order_by('id').first()
queue = VM.objects.get()
waitlist = int(pk) - int(next_q.id)
if waitlist == 1:
waitlist = 'You are next on the queue. :)'
return Response(
{"tokenid": token_id, "Runtestnow": False, "VMcount":
queue.vm_count,
'GridName': grid_name, 'waitlist #': waitlist, 'Vm_left':
queue.vm_count}, status=status.HTTP_201_CREATED)
Urls.py
path('checkstatus/<token_id>', CheckStatusView.as_view()),
We can always use the slug field, but I really wanted token_id as input. This should work fine for me as of now.
There might be some other way as well. Feel free to share.

Creating a Path Based on Multiple SlugFields in Django

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

How do I add together fields from a manytomanyfield in django?

I am creating a quote-generator in Django. I want to calculate the total of all items, insert it into a field, and save it.
The models are as follows:
from django.db import models
from django.core.urlresolvers import reverse
class Product(models.Model):
product_name = models.CharField(max_length=50)
product_description = models.CharField(max_length=200)
product_price = models.IntegerField(max_length=4)
def __unicode__(self):
return self.product_name
class Meta:
ordering = ('product_name',)
class Quote(models.Model):
quotee_name = models.CharField("Name", max_length=40)
quotee_email = models.EmailField("Email")
quotee_phone = models.IntegerField("Phone", max_length=10)
quotee_products = models.ManyToManyField(Product, verbose_name="Products")
quotee_total = models.IntegerField("Estimate", max_length=10, null=True, blank=True)
def __unicode__(self):
return self.quotee_email
class Meta:
ordering = ('quotee_email',)
def get_absolute_url(self):
return reverse('quote-detail', kwargs={'pk': self.pk, })
I am not using this through the Admin, so here is the forms.py:
from django import forms
from django.forms import CheckboxSelectMultiple
from InternalDusettenet.apps.quotes.models import Quote
class QuoteForm(forms.ModelForm):
class Meta:
model = Quote
fields = ('quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products')
widgets = {
'quotee_products': CheckboxSelectMultiple(attrs={'size': 10}),
}
And here is the views.py file. I have it set to just save a '1' into the form so that it actually saves. What I want is to replace the '1' with a function that returns the value of 'Product.product_price' for every one selected in 'Quote.quotee_products'. When I create a quote, I select the products, and it gives me the sum of all selected 'product_price' fields related to the selected products:
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView, DetailView
from django.shortcuts import Http404, get_object_or_404
from InternalDusettenet.apps.quotes.models import Quote
from InternalDusettenet.apps.quotes.forms import QuoteForm
class QuoteCreate(CreateView):
model = Quote
template_name = "quotes/quote_create_edit.html"
fields = ['quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products']
form_class = QuoteForm
def form_valid(self, form):
form.instance.quotee_total = 1
return super(QuoteCreate, self).form_valid(form)
class QuoteList(ListView):
model = Quote
template_name = "quotes/quote_list.html"
class QuoteDetail(DetailView):
model = Quote
template_name = "quotes/quote_detail.html"
class QuoteUpdate(UpdateView):
model = Quote
template_name = "quotes/quote_create_edit.html"
fields = ['quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products', 'quotee_total']
form_class = QuoteForm
class QuoteDelete(DeleteView):
model = Quote
success_url = '/'
template_name = "quotes/quote_delete.html"
I have read the Django docs MANY times but I have no clue how to do this one simple thing.
I am using Django 1.7 and Python 2.7.
No reason to save it in the database, just make it a method or property of the Quote object:
class Quote(models.Model):
...
def quotee_total(self):
return self.quotee_products.aggregate(total=models.Sum('product_price'))['total']
If need be, you can cache the value and fill the cache on the initial query:
class Quote(models.Model):
...
def quotee_total(self):
if not hasattr(self, '_quotee_total'):
self._quotee_total = self.quotee_products.aggregate(total=models.Sum('product_price'))['total']
return self._quotee_total
quotes = Quote.objects.annotate(_quotee_total=models.Sum('quotee_products__product_price'))
You can of course save that value in the database, but there's little reason. If you're worried about performance, that is better handled with caching than with saving the value to the database.
I would not calculate the total in a view. This makes more sense as a method.
class Quote(models.Model):
def calculate_quotee_total(self):
return sum(product.product_price for product in self.quotee_products.all())
def __save__(self):
self.quotee_total = self.calculate_quotee_total()
super(Quote, self).save()
Quote.quotee_total could also be calculated as needed, instead of saving it in the db.
class Quote(models.Model):
#property
def quotee_total(self):
return sum(product.product_price for product in self.quotee_products.all())

Categories