ForeignKey model query filter - django

Using filter(), how do I get the foreign key property 'recipient' of the current authenticated user ?
Models.py:
class Message(models.Model):
recipient = models.ForeignKey(CustomUser, on_delete = models.CASCADE,related_name = 'recipient',null = True)
sender = models.ManyToManyField(CustomUser,related_name = 'messages')
date = models.DateTimeField(auto_now_add=True, blank=True)
subject = models.CharField(max_length = 1000, blank = True)
message = models.TextField(blank=True, null=True)
unread = models.BooleanField(default = True)
class CustomUser(User):
user_bio = models.TextField(blank=True, null=True)
birth_date = models.DateField(null=True, blank=True)
def __str__(self):
return self.username
Views.py:
### Inbox list class
class InboxListView(ListView):
'''
This view lets the user view all the messages created in a list
'''
model = Message# [Message,SafeTransaction] # I want to be able to draw from two models/objects #
template_name = "myInbox/inbox.html"
paginate_by = 5
def get_context_data(self, **kwargs):
context = super(InboxListView, self).get_context_data(**kwargs)
context['message_list'] = Message.objects.filter(recipient=CustomUser.SOMETHING_idk)#INCORRECT FILTRATION, FIX ASAP!!!!!!!!!!!!!#
context['now'] = timezone.now()
context['safeTransaction_list'] = SafeTransaction.objects.all()
return context
Specifically this line is what I need puzzled out :
context['message_list']=Message.objects.filter(recipient=CustomUser.SOMETHING_idk)
What can I put as the filter parameter for a specific message recipient ?
I tried something along the lines of CustomUser or CustomUser.pk, or request.authenticated_as_the_thing_I_want_specifically. etcetera.
I seem to be a bit lost with it.
any help at all is appreciated.

It looks like you've created a custom auth user model? Assuming that's been setup correctly you should be able to do the following:
def get_context_data(self, **kwargs):
context = super(InboxListView, self).get_context_data(**kwargs)
context.update({
'message_list': Message.objects.filter(recipient=self.request.user),
'now': timezone.now(),
'safeTransactionList': SafeTransaction.objects.all(),
})
return context
Some additional things of note:
now is available as a django template tag. You can {% now %} in your template code.
With the way the Django ORM works you would be able to use the following in your template code {% for message in request.user.recipient.all %} or {% for message in request.user.messages.all %} instead of making the ORM call in get_context_data to iterate over message_list for the logged in user.

Related

Django Template Language: Create conditional for entire Model (not record by record)

Newbie problem. Per the following script in a template ...
{% if request.user not in Result %}
<p>You have no account in the system.</p>
{% endif %}
... the statement "You have no account in the system." is appearing 100 times on screen---because there are 100 records and therefore the condition is being checked 100 times.
Is there a way to modify the script so that the statement appears just once? Meaning, it checks the entire database once for evidence that request.user appears anywhere in the model in aggregrate (and not whether it appears in each of the 100 records in the database)?
Maybe there's an easier/better way to do this in views.py vs a template, but that's beyond my knowledge. Thank you. Below is the model called Result.
models.py
class Result(models.Model):
custom_user = models.ForeignKey(CustomUser, default=None,
null=True, on_delete=models.SET_NULL)
decision = models.ForeignKey(Decision, default=None,
null=True, on_delete=models.SET_NULL,
verbose_name="Decision")
vote_eligible = models.BooleanField(default=True)
vote = models.CharField(default="", max_length=100,
blank=True, verbose_name="Your
Vote:")
voted_already = models.BooleanField(default=False)
#staticmethod
def get_absolute_url():
return "/home"
def __str__(self):
return f"{self.custom_user}"
views.py
class VoteForm(LoginRequiredMixin, CreateView):
model = Result
form_class = VotingForm
template_name = 'users/vote_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["Result"] = Result.objects.all()
return context
forms.py
class VotingForm(forms.ModelForm):
class Meta:
model = Result
fields = ['decision', 'vote']
views.py
Since the requirement is to display whether the logged in user has account in 'Result' model or not. I have filtered the rows specific to the user. You can loop over the user_specific in your template. If user is present in 'Result' model 'user_specifc' will have elements. If user is not present in 'Result' table, 'user_specific' will be empty. In you template, you can check whether 'user_specific' is empty list or not.
class VoteForm(LoginRequiredMixin, CreateView):
model = Result
form_class = VotingForm
template_name = 'users/vote_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["Result"] = Result.objects.all()
context['user_specific'] = Result.objects.filter(custom_user=self.request.user)
return context
template.html
{% if not user_specific %}
<p>You have no account in the system.</p>
{% endif %}

How to pass pk of detail view into fk of form

Good afternoon, I am fairly new to Django and I am not sure how to go about this.I have a Django 2.2 project with these models:
class Equipment(models.Model):
name = models.CharField(
max_length=15,
unique=True,
verbose_name='asset name')
asset_cat = models.ForeignKey('category',on_delete=models.PROTECT,verbose_name='asset category')
asset_loc = models.ForeignKey('location',on_delete=models.PROTECT,verbose_name='asset location')
state = models.ForeignKey('status',on_delete=models.PROTECT,verbose_name='status')
brand = models.CharField(
max_length=15,
unique=False,
blank=True)
model = models.CharField(
max_length=12,
unique=False,
blank=True,
verbose_name='model number')
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
return reverse('equipment-detail', args=[str(self.id)])
class Meta:
ordering = ['asset_cat', 'name']
verbose_name_plural = 'pieces of equipment'
class Action(models.Model):
name = models.ForeignKey('equipment',on_delete=models.PROTECT,verbose_name='asset name',blank=False)
dt = models.DateTimeField(
auto_now_add=True,
verbose_name='date and time of incident')
incident = models.TextField(
blank=True,
null=True)
CHANGE = 'CHANGE'
SERVICE = 'SERVICE'
ACTION_CHOICES = (
(CHANGE, 'CHANGE'),
(SERVICE, 'SERVICE')
)
act = models.TextField(
blank=True,
choices=ACTION_CHOICES,
null=True,
verbose_name='action taken')
act_detail = models.TextField(
verbose_name='action detail',
blank=False)
result = models.TextField(
blank=True,
null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('service-detail', args=[str(self.id)])
class Meta:
ordering = ['-dt']
verbose_name_plural = 'service calls'
I have an Equipment Detail View like this:
class EquipmentDetailView(generic.DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get_context_data(self, **kwargs):
context = super(EquipmentDetailView, self).get_context_data(**kwargs)
return context
The detail view has two buttons: edit and service. If I click edit I have a model form that allows me to edit that instance of the Equipment model successfully.
However, when I click the service button, my form comes up to create an instance of the Action model, but when I submit it tells me that the null value in name_id violates the not null constraint.
It looks like my question is, how can I pass equipment.id from the Equipment Detail view to action.name of the action create form and keep the service button concept?
Action Form:
class ServiceForm(forms.ModelForm):
class Meta:
model = Action
fields = ['incident', 'act_detail', 'result']
Action (actually service) view:
class EquipmentServiceView(generic.CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
Assuming you don't want to go with simpliest solution to include name in form fields and have urls setup as:
/equipment/<id> - equipment detail view
/service - service (or action) create view
There are several ways of passing equipment id:
1) From url
We are going to change url to accept equipment_id. That means instead of /service you will have url /equipment/<equipment_id>/service.
Probably best solution - you will use URL according to REST architecture and will have very clear structure. Client can access page from anywhere (like just copy paste link from mail) and it will work.
urls.py:
urlpatterns = [
path('equipment/<int:pk>', EquipmentDetailView.as_view(), name='equipment-detail'),
path('equipment/<int:equipment_pk>/service', EquipmentServiceView.as_view(), name='service-create')
]
Your service button should look like this: service
and finally your view:
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.kwargs['equipment_pk']
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
2) Session data
In case you want to preserve service url without adding equipment_id, you can store equipment id either in session data(on your server) or in cookies(on client). That's not exactly good - client have to go to EquipmentDetailView prior to creating Service, but this will keep your urls intact.
views.py:
class EquipmentDetailView(DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
request.session['last_equipment_pk'] = self.object.pk
return response
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.request.session.get('last_equipment_pk')
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
P.S.: name is bad field name for ForeignField - should be something like equipment or so. Those labels usually associate with CharField and expected to be strings.

Display IntegrityError when trying to validate CreateView based on ModelForm using unique_together

How would I display an integrity error when using class based view's CreateView.
My current Model looks like this :
class Delivery(models.Model):
created_date = models.DateTimeField('date created', editable=False)
modified_date = models.DateTimeField('modified', editable=False)
user_name = models.ForeignKey(User, null=False)
stream_name = models.CharField(max_length=50, null=False)
view_name = models.CharField(max_length=100, null=False, blank=True)
activity_name = models.CharField(max_length=100, null=False, blank=True)
jira = models.URLField()
codereview = models.URLField()
related_streams = models.CharField(max_length=100, choices=sorted(streams()),blank=True)
description = models.TextField(null=False,blank=True)
status = models.BooleanField(default=False, blank=False)
And the corresponding view is :
class CreateEntryView(CreateView):
template_name = 'tracker/entry.html'
model = Delivery
success_url = reverse_lazy('table_view')
status = StreamStatus()
fields = ['stream_name','view_name','activity_name','jira','codereview','related_streams','description','status']
def get_initial(self):
if 'codereview-get' in self.request.GET:
parsedDict = codereviewParser(self.request.GET['codereview-get'])
return {'stream_name':parsedDict['stream'].split('_')[1:2][0],
'view_name':parsedDict['view'],
'activity_name':parsedDict['name'],
'jira':parsedDict['jira'],
'codereview':self.request.GET['codereview-get'],
'description':parsedDict['description'],
'status':parsedDict['status']}
else:
return self.initial.copy()
def form_valid(self, form):
form.instance.user_name = self.request.user
try:
return super(CreateEntryView, self).form_valid(form)
except IntegrityError as e:
messages.error(self.request, "Your data has not been saved!")
return HttpResponseRedirect(self.request.path)
return super(CreateEntryView, self).form_valid(form)
def get_context_data(self, **kwargs):
ctx = super(CreateEntryView, self).get_context_data(**kwargs)
ctx['locked'] = self.status.getLocked()
ctx['unlocked'] = self.status.getUnlocked()
return ctx
I tried a couple of techniques by passing a new context to render_to_response() but then I need to pass the entire context again. Also tried HttpResponse() which I dont like since it directs me to a blank page with a message. I would like to use an alert message to show the error to the user.
What about doing what form_invalid does, but adding your info to the context:
return self.render_to_response(self.get_context_data(form=form, integritymsg='Your data has not been saved!', reason=whatever))
And of course check and show integritymsg/reason in the template.
One thing not immediately obvious is that the arguments passed to get_context_data are added to the context. All the get_context_data being called along the MRO chain add their bit to the context.
ccbv.co.uk is a great tool. Unfortunately you need to inspect the code and find out the flow of execution yourself. It's not complicated, but yes, a diagram would help.
In general, start from as_view, dispatch, get, etc...

How do I limit the ListView slug to a number of choices in Django?

Here is the code
models.py
class Submission(models.Model):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
CAR_TYPES = (
(CAR, 'Car'),
(TRUCK, 'Truck'),
(VAN, 'Van'),
(SUV, 'SUV'),
)
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=MEDIA_TYPES, default=CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submission')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
views.py
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
def get_queryset(self):
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
return context
urls.py
url(r'^(?P<slug>[\w-]+)/$', SubmissionCategoryList.as_view(), name='submit_category'),
The code works fine. When I go to localhost:8000/car/ It shows the list view for only the CARS submission_type, etc. But, when I type in a url that isn't a part of the choices in CAR_TYPES, for example, localhost:8000/boat/, django still shows the template for this view. My question is: How do I limit the number of choices the slug should accept? And, if it is not a part of the CAR_TYPES choices, how do I get it to ignore this view?
Have you tried validating your "slug" in views?
views.py
def get_queryset(self):
if self.kwargs['slug'] is not None and self.kways['slug'].lower() in project.settings.CAR_TYPE:
# return your queryset.filter ...
else:
# return other template or redirect to other views.
project.settings.py
CAR_TYPE = ['car', 'truck', 'van', 'suv']
or, simply just edit url regex:
(?P<slug>car|truck|van|suv)
not the best approach but definitely solve your problem.
Ok so thanks to Anzel I figured out a great solution to this problem. I'm putting this here for anyone who needs this.
I created a reference.py file that looks like this. You can call it whatever you like, I just call it reference because it's what it does.
from enum import Enum
class CarTypes(Enum):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
I changed my models.py to import reference.py
I changed choices in submission_type to choices=tuple([(auto.name, auto.value) for auto in CarTypes])
from .reference import CarTypes
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=tuple([(auto.name, auto.value) for auto in CarTypes]), default=CarTypes.CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
And here is the final working views.py for categories
from .reference import CarTypes
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
CAR_TYPES = [auto.value for auto in CarTypes]
def get_queryset(self):
if self.kwargs['slug'] in self.CAR_TYPES:
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
else:
return Http404
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
context['submission_type'] = self.kwargs['slug']
return context
You can put this same queryset in a DetailView and it should work when a user searches site.com/car/2 or something like that

How to filter a (or all) joined table's rows by the current logged in user in django?

My creation is a basic project about ticketing, users and assets. It's a typical application that companies have to keep a list of who has what and what issues are occurring.
So far I have (models):
**** Tickets ******************
class Ticket(models.Model):
category = models.ForeignKey('TicketCategory')
issue = models.CharField(max_length=100)
user = models.ForeignKey('Users', blank=True,null=True,related_name="tickets")
owner = models.ForeignKey(User)
**** Users *********************
class Users(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
email = models.CharField(max_length=100, blank=True)
business = models.ForeignKey('Business', blank=True, null=True,related_name="users")
owner = models.ForeignKey(User)
class Meta:
db_table = 'users'
verbose_name_plural = "users"
ordering = ["lastname"]
**** Assets *********************
class Assets(models.Model):
serial = models.CharField(unique=False, max_length=100)
brand = models.CharField(max_length=100)
user = models.ForeignKey('Users', blank=True, null=True, related_name="assets")
location = models.ForeignKey('AssetLocation', blank=False, null=False, related_name="assets")
owner = models.ForeignKey(User)
I have stripped them down a bit to exclude the useless info. There are others also like AssetsLocations, Categories etc., all of them in the same pattern - the owner is added in the end as a Foreign Key.
I have created some form of authentication, so every logged in user will have his own tickets, assets, and users (employees actually). Filtering is needed so the data of each user ONLY are displayed after every successful authentication.
I am using CBVs and override the get_queryset to enable filtering by the user currently logged in:
Views.py
class TicketList(ListView):
template_name = 'assets/ticket_list.html'
def get_queryset(self):
user = self.request.user
return Ticket.objects.filter(owner_id=user.id).select_related('user','asset','category')
def get_context_data(self, **kwargs):
context = super(TicketList, self).get_context_data(**kwargs)
context['totalTickets'] = self.get_queryset().count()
context['tickets'] = self.get_queryset().select_related('user','asset','category')
return context
Everything works successfully and only logged_in user's data are shown. Then I am trying to create a new Ticket:
class TicketCreate(CreateView):
fields = [ 'category', 'issue', 'user']
template_name = 'assets/ticket_form.html'
def get_queryset(self):
user = self.request.user
return Ticket.objects.filter(owner_id=user.id)
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner_id = self.request.user.id
self.object.save()
return HttpResponseRedirect(self.get_success_url())
success_url = reverse_lazy('ticket_list')
I am getting everything posted in the form template by using {{ form.category }}, {{ form.issue }} etc. The issue I am facing is that the drop-down boxes displayed in the form, for example the {{ form.user }} should be displaying only the users where user.owner = self.request.user.id, in simple words: the users that the owner created. Instead of it, all the users in the database are displayed.
Isn't it obvious which is the question :) ?