Django - Pass two Models into one view, and display both models - python

I'm trying to pass two models into a create view, where i am trying to get the the primary key from the URL to retrieve the details from a food truck model so it can be displayed in the page, and where a user can write a review about food truck. Also, I'd like a list of the reviews to be displayed on the page.
views.py
class TruckReviewView(CreateView):
model = Review
template_name = 'truckReviews/detail.html'
fields = ['speedOfService', 'qualityAndTaste', 'valueForMoney', 'comment']
def get_queryset(self):
self.pk = self.kwargs['pk']
queryset = super(TruckReviewView, self).get_queryset()
return queryset
def get_context_data(self, **kwargs):
context = super(TruckReviewView, self).get_context_data(**kwargs)
context['truck'] = FoodTrucks.objects.get(truckID=get_queryset())
context['reviews'] = Review.objects.get(truckID=get_queryset())
return context
urls.py
urlpatterns = [
path('', TruckListView.as_view(), name='reviews-home'),
path('truck/<int:pk>/', TruckReviewView.as_view(), name='truck-detail'),
path('about/', About.as_view(), name='reviews-about'),
]
models.py
class FoodTrucks(models.Model):
truckID = models.IntegerField(primary_key=True, unique=True, null=False)
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatarSRC = models.TextField(default=None)
avatarALT = models.CharField(max_length=20, default=None)
avatarTitle = models.CharField(max_length=20, default=None)
coverPhotoSRC = models.TextField(default=None)
coverPhotoALT = models.CharField(max_length=20, default=None)
coverPhotoTitle = models.CharField(max_length=20, default=None)
website = models.TextField(default=None)
facebook = models.CharField(max_length=100, default=None)
instagram = models.CharField(max_length=30, default=None)
twitter = models.CharField(max_length=15, default=None)
class Review(models.Model):
reviewID = models.AutoField(primary_key=True, unique=True, serialize=False, null=False)
truckID = models.ForeignKey(FoodTrucks, on_delete=models.CASCADE)
userID = models.ForeignKey(User, on_delete=models.CASCADE)
datePosted = models.DateTimeField(default=timezone.now)
speedOfService = models.IntegerField()
qualityAndTaste = models.IntegerField()
valueForMoney = models.IntegerField()
comment = models.TextField(max_length=128)
I've tried to use get_queryset to get the pk from the URL and pass the pk into get_context_data and target the specific truck with that ID in the database.

The difficulty comes from the fact that you combine a list view and create view. If you want to combine this into one view, then you need to do a bit mixing and matching with different mixins of the Class Based Views.
It can be done, but it's not trivial. If you're new to Django, then this may be overshooting things. I've renamed fields an such and did it as an exercise. I haven't bothered with the form submission, it shouldn't be that difficult to implement as the other views don't deal with the methods involved (form_valid, get_success_url, etc). You can use it as a guide to see what you should be learning. The above linked site is extremely convenient to see how things are mixed together.
The result below will provide the variables "foodtruck", "reviews" and "form" to the template.
import typing as t
from django.views import generic
from .models import FoodTruck, Review
from .forms import ReviewForm
if t.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse
from django.contrib.auth.models import AbstractUser
class AuthenticatedRequest(HttpRequest):
user: AbstractUser = ...
class FoodTruckDetailReviewListCreateView(
generic.list.MultipleObjectMixin, generic.edit.CreateView,
):
template_name = "foodtrucks/detail.html"
model = Review
list_model = Review
context_list_name = "reviews"
context_object_name = "foodtruck"
detail_model = FoodTruck
form_class = ReviewForm
def get(self, request: "AuthenticatedRequest", *args, **kwargs) -> "HttpResponse":
"""
Combine the work of BaseListView and BaseDetailView
Combines the get implementation of BaseListView and BaseDetailView, but
without the response rendering. Then hands over control to CreateView's
method to do the final rendering.
Some functionality is stripped, because we don't need it.
:param request: The incoming request
:return: A response, which can be a redirect
"""
# BaseListView
self.object_list = self.get_queryset()
# BaseDetailView
self.object = self.get_object()
context = self.get_context_data(
object=self.object, object_list=self.object_list
)
# CreateView sets self.object to None, but we override form_kwargs, so
# we can leave it at a value.
return self.render_to_response(context=context)
def get_template_names(self):
# Bypass logic in superclasses that we don't need
return [self.template_name]
def get_object(self, queryset=None):
# We provide the queryset to superclasses with the other model
return super().get_object(queryset=self.detail_model.objects.all())
def get_queryset(self):
# This only gets called by MultipleObjectMixin
pk = self.kwargs.get(self.pk_url_kwarg)
if pk is None:
raise AttributeError(
"Unable to filter on food truck: {} is missing in url.".format(
self.pk_url_kwarg
)
)
queryset = self.list_model.objects.filter(food_truck_id=pk)
# print(str(queryset.query))
return queryset
def get_context_data(self, **kwargs):
if "object" in kwargs:
kwargs[self.context_object_name] = kwargs["object"]
if "object_list" in kwargs:
kwargs[self.context_list_name] = kwargs["object_list"]
return super().get_context_data(**kwargs)
def get_form_kwargs(self):
# Bypass ModelFormMixin, which passes in self.object as instance if it
# is set.
return super(generic.edit.ModelFormMixin, self).get_form_kwargs()
And as a reference, this is what I changed the models to:
import uuid
from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone
class FoodTruck(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatar_url = models.URLField(blank=True)
avatar_alt_text = models.CharField(max_length=20, blank=True)
avatar_title = models.CharField(max_length=20, blank=True)
cover_photo_url = models.URLField(blank=True)
cover_photo_alt_text = models.CharField(max_length=20, default="No photo provided")
cover_photo_title = models.CharField(max_length=20, default="No photo provided")
website = models.URLField(blank=True)
facebook = models.CharField(max_length=100, blank=True)
instagram = models.CharField(max_length=30, blank=True)
# https://9to5mac.com/2017/11/10/twitter-display-name-limit/
twitter = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Review(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
food_truck = models.ForeignKey(
FoodTruck, on_delete=models.CASCADE, related_name="reviews"
)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
posted_at = models.DateTimeField(default=timezone.now)
speed_of_service = models.IntegerField()
quality_and_taste = models.IntegerField()
value_for_money = models.IntegerField()
comment = models.TextField(max_length=128)
def __str__(self):
return "Review about {} by {}".format(
self.food_truck.name, self.user.get_full_name()
)
And finally the form (with a bit of trickery to inject bootstrap classes):
class ReviewForm(forms.ModelForm):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for field in self.fields.values():
if not field.widget.is_hidden:
field.widget.attrs.setdefault("class", "form-control")
class Meta:
model = Review
exclude = ("uuid", "user", "food_truck", "posted_at")

First, there's not need to create a truckID and reviewID primary key fields because Django creates a unique id field for each object automatically on which you can simply do .get(id=1) or .filter(id=1) etc.
Just like it is completely useless to put ID in fields with Foreign Key or any relational fields because Django will automatically take the name and append _id to it. For instance, just user would become user_id or truck would be truck_id in backend on which you can do .get(user__id=1) or .get(user_id=1) for example.
You should review this section of your code. You're actually not doing anything with the primary key:
def get_queryset(self):
queryset = super().get_queryset()
try:
item = queryset.get(id=self.kwargs['pk'])
except:
...
else:
# Do something with item here
...
finally:
return queryset
or, with get_context_data:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = super().get_queryset()
try:
item = queryset.get(id=kwargs['pk'])
except:
...
else:
# Do something with item here
context['item'] = item
finally:
return context

I'm sorry but your question is a bit confusing. If you're trying to get details from a model you should use a DetailView. Also, on a DetailView assuming you want the details of a Review since you have the truck on a review you could simply override the get_context_data and set truck in the context by doing self.object.truck.
If you're trying to create a review then it's right to use the CreateView but that should only be for the Review model.
To list you should use a ListView.
So, to my understanding, you have a truckID and want to create a review for that. In that case, it'd have a CreateView for Review model.
Have a look at the CreateView, DetailView and ListView docs

Related

Generic detail view must be called with either an object pk or a slug in the URLconf

Django is giving me the following error when I try to access /domains/{domain}/{host}:
Generic detail view HostDetailPageView must be called with either an object pk or a slug in the URLconf.
It appears that DetailView is expecting something different than what I am providing.
urls.py
urlpatterns = [
path('domains/', DomainsPageView.as_view()),
path('domains/<str:domain>', DomainHostsPageView.as_view()),
path('domains/<str:domain>/<str:host>', HostDetailPageView.as_view()),
path('', TemplateView.as_view(template_name="hosts/index.html"))
]
views.py
class HostDetailPageView(DetailView):
template_name = 'hosts/hostdetail.html'
model = Host
# Populates list of enabled domains in the context
def get_queryset(self):
qs = super().get_queryset()
filtered = qs.filter(name=self.kwargs['host'])
if not filtered.exists():
raise Http404("Host does not exist")
# filter by a variable captured from url, for example
return filtered.first()
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs)
models.py
class Host(models.Model):
created = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
name = models.CharField(unique=True, max_length=settings.MAX_CHAR_COUNT)
ip_addresses = models.ManyToManyField(IPAddress)
services = models.ManyToManyField(Service)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
os = models.ForeignKey(OperatingSystem, on_delete=models.CASCADE, blank=True, null=True)
ciphers = models.ManyToManyField(Cipher, blank=True)
certificate = models.ForeignKey(Certificate, on_delete=models.CASCADE, blank=True, null=True)
class Meta:
ordering = ['name']
Set the pk_url_kwarg attribute and override the get_queryset(...) method of the view
class HostDetailPageView(DetailView):
template_name = 'hosts/hostdetail.html'
model = Host
pk_url_kwarg = 'name'
def get_queryset(self):
return super().get_queryset().filter(host__name=self.kwargs['host'])

Check if user is in the manager column in order to create a post/task

I'm learning in DJango and I have learned alot of stuff from the documentation and also in StackOverflow. Right now, I'm kinda stuck and I just want to know who can I check in a class based view, if the user is in the manager column in job model/ It can also be in the manager model that's fine too.
I tried using UserPassesTestMixinin order to check if user is part of it but I'm getting an error of Generic detail view createjob must be called with either an object pk or a slug in the URLconf.
I just need someone to point me to the right direction or give me a hint.I also tried, this:
class createjob (LoginRequiredMixin,CreateView):
model = Job
fields = ['member','title', 'description', 'file']
def form_valid(self,form):
form.instance.manager=self.request.user
return super().form_valid(form)
But it's giving me an error of Cannot assign "<SimpleLazyObject: <User: edlabra>>": "Job.manager" must be a "Manager" instance.
Here's my views.py:
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import ListView, CreateView
from .models import Job, Member
from profiles.models import User
from django.contrib.auth.decorators import login_required
# Create your views here.
class jobs(LoginRequiredMixin,ListView):
model = Job
template_name = 'users/user_jobs.html'
context_object_name = 'jobs'
def get_queryset(self):
return Job.objects.filter(member__member=self.request.user)
class createdjobs(LoginRequiredMixin,ListView):
model = Job
template_name = 'users/manager_jobs.html'
context_object_name = 'jobs'
def get_queryset(self):
return Job.objects.filter(manager__manager=self.request.user)
class teamview(LoginRequiredMixin,ListView):
model = Member
template_name = 'users/manage_team.html'
context_object_name = 'members'
def get_queryset(self):
return Member.objects.filter(manager__manager=self.request.user)
class createjob (LoginRequiredMixin,UserPassesTestMixin,CreateView):
model = Job
fields = ['member','title', 'description', 'file']
def test_func(self):
job=self.get_object()
if self.request.user == Job.manager:
return True
return False
Models.py:
from django.db import models
from profiles.models import User
# Create your models here.
class Points (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
points = models.IntegerField(default=0, null=False)
def __str__(self):
return self.user.username
class Profile (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.png',upload_to='profile_pics')
def __str__(self):
return f'{self.user.username}Profile'
class Manager (models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
manager = models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return self.name
class Member (models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
manager = models.ForeignKey(Manager, on_delete=models.CASCADE)
member = models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return self.name
class Job (models.Model):
manager = models.ForeignKey(Manager, on_delete=models.CASCADE)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
title = models.CharField(max_length=30, blank=False, null=False)
description = models.TextField()
datePosted = models.DateTimeField (auto_now = True)
file = models.FileField(null=True, blank=True,upload_to='job_files')
def __str__(self):
return self.title
assign user from manager table.
def form_valid(self,form):
form.instance.manager=Manager.objects.get(manager=self.request.user)
return super().form_valid(form)

How do I pass only current user data to be filtered in django-filter?

I am using django-filter to filter my data. The issue is both the available options of the filters and the results are based on all the data, not only on the curent user.
models.py
class Product(models.Model):
title = models.CharField(max_length=264)
description = models.CharField(max_length=264)
date_added = models.DateField(auto_now_add=True)
time_added = models.TimeField(auto_now_add=True)
user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
I used the code below to filter the results only on the current user:
filters.py
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
#property
def qs(self):
parent = super(ProductFilter, self).qs
user = getattr(self.request, "user", None)
return parent.filter(user=user).order_by("-timestamp")
In my views.py I tried to pass the queryset, but nothing changed:
class ProductsFilterView(LoginRequiredMixin, FilterView):
model = Product
filterset_class = ProductFilter
def get_queryset(self, **kwargs):
return self.model.objects.filter(user=self.request.user)
I am not sure how to limit the available filter options only on the current user's data.
So, summarizing how can I pass into the filterset just the current user's data and not pass the whole data into the filterset and then filter them on current user's data.
filters.py
class ProductFilter(django_filters.FilterSet):
class Meta:
title = django_filters.CharFilter(lookup_expr='icontains')
# And you can also do "lookup_expr='year__gt' and also 'year__lt'"
date_added = django_filters.NumberFilter(name='date_added', lookup_expr='year')
class Meta:
model = Product
fields = [] #You can add fields that wants to be displayed
views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from .filters import ProductFilter
# Function Base View
#login_required
def productfilter(request):
user = request.user
product_list = Product.objects.filter(user=user)
product_filter = ProductFilter(request.GET, queryset=product_list)
return render(request, "template_name.html", {'products': product_list})
#Class Based View
class ProductsFilterView(LoginRequiredMixin, FilterView):
context_object_name = 'products'
template_name = 'template_name.html'
filter_set = ProductFilter
def get_queryset(self):
return Product.objects.filter(user=self.request.user)
I hope this was helpful.

Django Rest API TypeError when using ChoiceField

I encountered a TypeError when trying to use the ChoiceField in my serializers.
from rest_framework import serializers
from rest_framework.fields import ChoiceField
from comp_track_sys.models import Campaign
from contacts.models import CList
class EditCampaignSerializer(serializers.ModelSerializer):
class Meta:
model = Campaign
...
def __init__(self, *args, **kwargs):
super(EditCampaignSerializer, self).__init__(*args, **kwargs)
user = self.context['request'].user
clists = CList.objects.filter(company=user.profile.company)
self.fields['clist'] = ChoiceField(choices=clists)
I have tried to do ChoiceField(choices=json.dumps(clists)), but still got the error.
TypeError: <CList: Testing> is not JSON serializable
These are my models in case it is needed to resolve the issue.
# Campaign Model
class Campaign(models.Model):
campaign_id = UUIDField(auto=True)
name = models.CharField("Campaign Name", max_length=50)
company = models.ForeignKey(Company)
clist = models.ForeignKey(CList, verbose_name="Contact List", blank=True, null=True)
user = models.ForeignKey(User, blank=True, null=True)
# CList Model
class CList(models.Model):
name = models.CharField("Contact List", max_length=255)
description = models.TextField(blank=True, null=True)
contacts = models.ManyToManyField(Contact, related_name='con_clists')
company = models.ForeignKey(Company)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
uuid = models.CharField("ShortUUID", max_length=50, blank=True, null=True)
This is the views
class EditCampaignViewSet(ModelViewSet):
queryset = Campaign.objects.all()
serializer_class = EditCampaignSerializer
parser_classes = (MultiPartParser, FormParser)
def list(self, request, type=None, *args, **kwargs):
company = request.user.company
queryset = Campaign.objects.filter(company=company,
campaign_type=type)\
.order_by('-created')\
.prefetch_related('user__profile')
serializer = EditCampaignSerializer(queryset, many=True, context={'request': self.request})
return Response(serializer.data)
I think you should be using a PrimaryKeyRelatedField rather than a ChoiceField, and defining a custom get_queryset method on the serializer field to filter the relevant QuerySet.
from rest_framework import serializers
from rest_framework.fields import ChoiceField
from comp_track_sys.models import Campaign
from contacts.models import CList
class CListPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return self.queryset.filter(
company=self.root.context['request'].user.profile.company
)
class EditCampaignSerializer(serializers.ModelSerializer):
clist = CListPrimaryKeyRelatedField(
queryset=CList.objects.all(),
)
class Meta:
model = Campaign
fields = (
'clist',
)
When you create a ChoiceField you need to make sure that the choices keyword is some iterable. By using json.dumps(clists) I imagine you were trying to convert your clists into an iterable however, the CList type cannot be JSON serialized because it isn't recognized by the json library.
Once you pick a field you want to set as valid choices you might be able to try something like this, for example if the field you wanted to choose from was the primary key:
ChoiceField(choices=list(clists.values_list('pk', flat=True)))
Since the .values_list method returns an iterable, this will be fine to pass to choices=.
Hope I helped.

python django models request.user

I am trying to implement a model that automatically filters data based on the user. I was able to modify the user model and add a value for "company" that references a table of companies.
I have a table that has a company value as well (call it data_table). Ideally when ANY user logs into the system, the data filtered to match company value on user to company value on data_table.
So far I have it working, but I want to overwrite the get_query method to incorporate what I have and I can't figure out how to get the current logged in user into the model without passing it from the view.
I would think that in the below I could just use in models.py:
current_user = request.user
cid = current_user.company.id
The advantage being I don't have to remember to put the filter in when I use the model.
The error that gets thrown is:
Exception Value:
global name 'request' is not defined
Here is what works:
models.py:
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import AbstractUser
class Company(models.Model):
name = models.CharField(max_length=40, blank=True)
def __str__(self):
return self.name
class User(AbstractUser):
company = models.ForeignKey(Company, on_delete=models.CASCADE, default=1)
class data_tableManager(models.Manager):
def get_queryset(self, cid):
return super(data_tableManager, self).get_queryset().filter(company_filter_id=cid)
class data_table(models.Model):
company_filter = models.ForeignKey(Company, on_delete=models.CASCADE, default=1)
data_column = models.CharField(db_column='dc', max_length=10, blank=True, null=True)
objects = models.Manager() # would like to replace this with data_tableManager
company_objects = data_tableManager() # The company specific manager.
def __str__(self):
return self.firstname
views.py
from .models import data_table
#login_required(login_url='/login/')
def data_view(request):
current_user = request.user
cid = current_user.company.id
print current_user.get_company()
return render(
request,
'example.html',
{
'filtered1': data_table.objects.filter(company_filter_id=cid),
'filtered2': data_table.company_objects.get_queryset(cid),
'cid':cid,
}
)

Categories