Best way to filter on data belonging to the logged in user (Django) - python-3.x

First of all; I'm fairly new to Django.
Right now, I'm trying to create a very simple webpage with links (just to learn). The idea right now is, that a user (which is logged in) can add links to the database/model, and only see the links of which he has added.
I'm strugling to figure out what the best practice is for that - is it to store the user.username in the model, and then make a .filter(username=user) each time or..? I would assume Django has some (faster way) of handling this.
I have the following
models.py
from django.db import models
from django.contrib.auth.models import User
class links(models.Model):
link = models.URLField()
#user = <something_here>
views.py
def add_link(request):
if request.method == "POST":
form = add_link_form(request.POST)
if form.is_valid():
messages.success(request, "Link is added!")
form.save()
return redirect("my_links")
else:
form = add_link_form()
context = {
"links":links.objects.all(),
"form":form
}
return render(request, "django_project/my_links.html",context=context)
my_links.html
{% extends "django_project/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Add link</button>
</div>
</form>
{% for l in links%}
{{l.link}}
{% endfor%}
{% endblock content %}

You can define Link as:
class Link(models.Model):
link = models.URLField()
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="links")
where, when adding you could do:
logged_in_user.links.create(...)
or if adding an already existing link:
logged_in_user.links.add(link)
or if you know that links will forever remain as as strings without any other data related to it, and you use postgresql as your DB, you could add this field to the user model:
from django.contrib.postgres.fields import ArrayField
from django.db import models
class UserModel(models.Model):
...
links = ArrayField(models.URLField(), default=list)
and when adding, treat it exactly as you would do with an array:
logged_in_user.links.append("google.com")
logged_in_user.save()
Both of these approaches, in essence, provide you a filtered list when calling logged_in_user.links, but in the first case it is a queryset of Link objects, whereas the second is an array of strings.

Related

How do I update Django database from field choices?

I'm developing a CRM using django and for my lead details I am trying to display my choices in the html form but also have it update the database when a new choice is selected and saved. Currently, I am able to display the choice that was selected upon lead creation, but I don't know how to allow the agent to change the choice and have that new choice update the database. I am still in the process of learning Django so theres still a lot i'm learning as i go.
views.py
class LeadDetailView(LoginRequiredMixin, DetailView):
template_name = "lead-details.html"
queryset = Lead.objects.all()
context_object_name = "leads"
models.py
class Lead(models.Model):
PROBLEM_LEAD = 'Problem Lead'
PAY_TOO_FAR = 'Payments Too Far'
PAY_TOO_SMALL = 'Payments Too Small'
NPI = 'No Payment Information'
ACTIVE = 'Active Deal'
NO_DEAL = 'No Deal'
choices_lead_status = [
(PROBLEM_LEAD, 'Problem Lead'),
(PAY_TOO_FAR,'Payments Too Far'),
(PAY_TOO_SMALL,'Payments Too Small'),
(NPI,'No Payment Information'),
(ACTIVE,'Active Deal'),
(NO_DEAL,'No Deal')
]
choices_lead_owner = [
("Settlement, New",'Settlement, New'),
("Lead, Sales",'Lead, Sales'),
]
lead_status = models.CharField(max_length=100, choices=choices_lead_status, blank=True)
lead_owner = models.CharField(max_length=100, choices=choices_lead_owner, blank=True)
agent = models.ForeignKey(Agent, null=True, on_delete=models.SET_NULL, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
html
# this displays the lead_owner but i also need it to iterate through the choices to allow proper selection
<div id="select">
<div class="form-group">
<label for="input-select">Lead </label>
<select class="form-control" id="input-select" >
<option>{{ leads.lead_owner}}</option>
<!-- <option>Lead, Sales</option>
<option>Settlement, New</option>
<option>Corden, James</option>
<option>Rower, Charles</option>
<option>Has to be dynamic</option> -->
</select>
</div>
</div>
There are a few things to take into account and more than one way (as always) to accomplish it. Here's my suggestions:
First, update your HTML to use Django tags with a for loop and iterate your lead_owner choices. If the value matches the existing Lead_owner value, it's pre-selected:
<div id="select">
<div class="form-group">
<label for="input-select">Lead Owner</label>
<select class="form-control" id="input-select" name="lead_owner">
{% for choice in leads.choices_lead_owner %}
{% if choice.0 == leads.lead_owner %}
<option value="{{ choice.0 }}" selected>{{ choice.1 }}</option>
{% else %}
<option value="{{ choice.0 }}">{{ choice.1 }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
Submitting and updating the form can be done as such by inheriting from your LeadDetailView. I included the imports as well:
from django.views.generic.edit import UpdateView
from django import forms
class LeadUpdateView(LoginRequiredMixin, UpdateView):
model = Lead
form_class = LeadUpdateForm
template_name = "lead-update.html"
success_url = reverse_lazy('lead_detail')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['leads'] = self.object
return context
Now, you can create a new template and extend your existing HTML template like so by :
{% extends 'lead-details.html' %}
{% block content %}
<h1>Update Lead</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}
Hope this helps.
Edited for your question:
You could use the existing view and form.
You can edit the LeadDetailView to be something like this:
class LeadDetailView(LoginRequiredMixin, DetailView):
template_name = "lead-details.html"
queryset = Lead.objects.all()
context_object_name = "leads"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = LeadOwnerForm(instance=context['leads'])
return context
def post(self, request, *args, **kwargs):
lead = self.get_object()
form = LeadOwnerForm(request.POST, instance=lead)
if form.is_valid():
form.save()
messages.success(request, "Lead updated successfully!")
else:
messages.error(request, "Lead update failed.")
return redirect('lead_detail', pk=lead.pk)
And, then the form can be adjusted like so:
{% block content %}
{{ leads.first_name }} {{ leads.last_name }}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}
Another approach would be using Ajax, which is a bit more complicated but offers some solid benefits:
The page does not need to refresh upon submitting the form. The backend will respond with Json too, which is something I prefer as your app evolves.
Ajax means that you'll ask for less data, typically, to be returned to the frontend. It doesn't need to send back the entire template, but instead only the updated data or data you're choosing to return.
Ajax can be implemented with limited Javascript experience, but having JS experience allows you to do more with it.
I recommend getting to become familiar with implementing the above and then evolve your app to use Ajax, but that's me. I also prefer to use function based views instead of class based views when starting with Django, so I can really see what's happening before it's mostly hidden in class based views. But, Django is all about doing more with less, so learning CBV's out of the gate is not a bad thing at all.

Delete all task related to User

I am creating a todo app. I want to delete all task related to a particular user from Tasklist table after login but i got an error ' deleteAll() missing 1 required positional argument: 'id' ". How can i delete all task. Thanks in advance.
Model.py
from django.contrib.auth.models import User
from django.db import models
class TaskList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
task = models.CharField(max_length=100, null=False, blank=False)
complete = models.BooleanField(default=False)
taskCreate = models.DateTimeField(auto_now=True)
Views.py
def deleteAll(request, id):
TaskList.objects.filter(id=request.user.tasklist_set.filter(id)).delete()
return redirect('todolist')
Html
{% extends 'home.html' %}
{% block content %}
{%if user.is_authenticated%}
<h2>Welcome {{ user.get_username| title }}</h2>
<button type="submit">Logout</button>
<form method="post">
{% csrf_token %}
<label for="task">
<input name="task" placeholder="add today's task" id="task" required>
<button type="submit">add</button>
</label>
</form>
<form method="get" >
{% csrf_token %}
<div>
{% for todo in task %}
{% if todo.complete %}
<li class="list-group-item todo-completed"><h6>{{ todo.task|linebreaks}} </h6></li>
{% else %}
<h6 class="list">{{ todo.task|linebreaks}}</h6>
{% endif %}
{% endfor %}
<button> Delete All </button>
<button> Delete Completed </button>
</div>
</form>
{% endif %}
{% endblock %}
First Change in your template Delete All button. Because your view requires a user id.
<button type="button"> Delete All </button>
Then simplify the deleteAll functions db query. You need to filter all tasks of current user. so you can just use user__id__exact = id:
def deleteAll(request, id):
TaskList.objects.filter(user__id__exact = id).delete()
return redirect('todolist')
Alternative Solution:
You don't need to send id. because you can get the current user id in request argument.
You just need to change the filter:
# view
#login_required
def deleteAll(request):
TaskList.objects.filter(user = request.user).delete()
return redirect('todolist')
# In template use:
<button type="button"> Delete All </button>
# urlpattern
path('delete/', deleteAll, name='delete')
I hope this will help you. Thank you. Happy Coding :)
This is a very common mistake that people do. :)
You have defined a function which takes request and an argument named as id as shown here:
def deleteAll(request, id):
TaskList.objects.filter(id=request.user.tasklist_set.filter(id)).delete()
return redirect('todolist')
But you have to pass an argument into deleteAll. To do that you have to type in the argument value.
You can do so by entering the value after {% url 'delete' %} in the line
Delete All
For example:
Delete All
I hope this helped, if not, feel free to comment and clarify the query.
I suppose "id" refers to the user's id, that is request.user.pk.
There are two issues, or two solutions:
About the Error
You have specified that your view expects the argument id, therefore you would need to add it to the URL in the template.
def deleteAll(request, id): # <<<< "id" is specified as argument
Delete All <!-- no "id" is specified -->
If you have an argument in a view, you need to specify it in the urls.py (which you obviously did) and subsequently whereever you need it reversed:
Delete All
You need the request template processor to have the request in your template context.
However, you actually don't need the ID because you know it in the view already. This is the "other solution".
About the ID
def deleteAll(request, id):
TaskList.objects.filter(id=request.user.tasklist_set.filter(id)).delete()
return redirect('todolist')
can be rewritten to
from django.contrib.auth.decorators import login_required
#login_required
def deleteAll(request):
TaskList.objects.filter(id=request.user.pk).delete()
return redirect('todolist')
You need to remove the id argument in the url in your urls.py, as well.

ModelForm Fields are not pre-populated with existing data during updating in Django

I want to update the User and Lab model. I am able to see the form but it is not pre-populated with existing database information even after setting the instance parameter. If I submit a blank form then all fields are reset to blank values in the database. I have tried several solutions available online but nothing works.
My queries -
How do I pre-populate my form with existing data?
If the user doesnt fill out a particular field, I want the previous information to be stored as it is and not as a blank value. How do I achieve this?
I have the following models.py
class Lab(models.Model):
uid = models.OneToOneField(User, on_delete=models.CASCADE)
company=models.CharField(max_length=200,blank=True)
#receiver(post_save, sender=User)
def create_lab_profile(sender, instance, created, **kwargs):
if created:
Lab.objects.create(uid=instance)
#receiver(post_save, sender=User)
def save_lab_profile(sender, instance, **kwargs):
instance.lab.save()
Forms.py
class UserForm(forms.ModelForm):
email=forms.EmailField(max_length=300)
class Meta:
model = User
fields = ('first_name', 'last_name', 'email',)
class LabForm(forms.ModelForm):
class Meta:
model = Lab
fields = ('company',)
views.py
#login_required
def update_user_details(request,pk):
if request.method == 'POST':
user_form = UserForm(request.POST,instance=request.user)
lab_form = LabForm(request.POST,instance=request.user.lab)
if user_form.is_valid() and lab_form.is_valid():
user_form.save()
lab_form.save()
messages.success(request,'Your profile was successfully updated!')
return redirect('user_details')
else:
messages.error(request,('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
lab_form = LabForm(instance=request.user.lab)
return render(request, 'update_user_details.html', {'user_form': user_form,'lab_form': lab_form})
template -
{% extends 'base.html' %}
{% block content %}
{% csrf_token %}
<H3> Update Personal information - </H3>
<br>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ user_form.as_p }}
{{ lab_form.as_p }}
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
{% endblock %}
Any help/suggestions will be appreciated!

Can't delete an object in delete_view, in Django

I'm trying to create a delete view for my product form, my problem is with the submit button, it doesn't delete my object, it just redirects me to previous page.
This a Windows 7 machine, Python 3.7.1, Anaconda 2018.12
I've tried eliminating the line: if request.method == "POST"
And without that line the object is eliminated, so I think the problem is with the if statement or the POST method, but couldn't solve it
views.py:
from django.shortcuts import render, get_object_or_404, redirect
from .forms import ProductForm, RawProductForm
from .models import Product
def product_delete_view(request, id):
obj = get_object_or_404(Product, id=id)
if request.method == "POST":
obj.delete()
return redirect('../')
context = {
"object": obj
}
return render(request, "products\\product_delete.html", context)
product_delete.html:
{% extends 'base.html' %}
{% block try %}
<form action='.' method='POST'>{% csrf_token %}
<h1>Do you want to delete the product "{{ object.title }}"?</h1>
<p>
<input type="submit" value="Yes" />
Cancel
</p>
</form>
{% endblock %}
The idea is that when I click "Yes", the object disappears, but instead I am redirected to the page of the actual object I wanted to delete

Django 1.11: doesn't clean form field

everyone!
Implementing the edit/add functions to the app, I found that Django ignores my clean_<field_name> method in some classes. When I tried to debug it I found out that the methods are not even called. Here are the relevant code snippets:
models.py
class StoredItem(models.Model):
item = models.ForeignKey(Item)
quantity = models.IntegerField(default=0)
class Meta:
ordering = ["item"]
def __str__(self):
return "{0} - {1}, {2} pcs.".format(self.item.serial, self.item.name, self.quantity)
def get_absolute_url(self):
return reverse('almacen:storeditem-detail', args=[str(self.id)])
forms.py
# Stored items related forms
class NewOrEditStoredItemForm(forms.Form):
item = forms.ModelChoiceField(queryset=StoredItem.objects.all())
quantity = forms.IntegerField()
def clean_item(self):
item = self.cleaned_data['item']
storeds = map(lambda x: x.item, StoredItem.objects.all())
if item in storeds:
raise ValidationError(_("This item has already been stored - you only can change its quantity in the stock!"))
def clean_quantity(self):
quantity = self.cleaned_data['quantity']
if quantity < 0:
raise ValidationError(_("Items quantity can not be negative!"))
return quantity
views.py
# Stored items related ---------------------------------------------------------------------
class CreateStoredItem(CreateView):
model = StoredItem
fields = '__all__'
class UpdateStoredItem(UpdateView):
model = StoredItem
fields = '__all__'
storeditem_form.html
{% extends 'base_template.html' %}
{% block header %}
<h2 id="center">Stocked item add/delete </h2>
{% endblock %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit"/>
</form>
{% endblock %}
EDIT
Resolved by rewriting the classes into functions. For classes I was recommended to use validators, but anyway, functions worked as I need.
Resolved by rewriting the classes into functions. For classes I was recommended to use validators, but anyway, functions worked as I needed

Resources