How can I update a many to many field in Django - python

I am trying to update a many to many field button when a user clicks a button
The Payment model is to allow users to submit their receipts to be reimbursed. Each Payment has a "many to many" field named status. Currently, there are only two statuses a payment can have "complete" or "incomplete". When a user submits a payment the default is incomplete, and when the company issues a check to reimburse the user, the company needs to be able to click a button that will update the payment to 'complete'.
To get a better idea of what this looks like, the HTML page looks like this.
The Page
My models.py
from django.db import models
from django.contrib.auth.models import User
import uuid
from django.urls import reverse
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return f'{self.name}'
class Status(models.Model):
type = models.CharField(max_length=100)
def __str__(self):
return f'{self.type}'
class Payment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
amount = models.DecimalField(max_digits=14, decimal_places=2)
receipt_img = models.FileField(default='default.jpg', upload_to='receipt_pics')
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
type = models.ManyToManyField(Category)
time_posted = models.DateTimeField(auto_now_add=True)
description = models.TextField(null=True)
status = models.ManyToManyField(Status)
def __str__(self):
return f'{self.id} Contribution'
def get_absolute_url(self):
return reverse('contrib-home')
The button that sends the request in payment_detail.html
<a href="{% url 'update-status' operation='complete' pk=object.id %}">
<button type="button" class="btn btn-primary">Complete</button>
</a>
The urls.py
path(r'^connect/(?P<operation>.+)/(?P<pk>\d+)/$', views.update_status, name='update-status')
The views.py
class PaymentDetailView(DetailView):
model = Payment
template_name = 'financial/payment_detail.html'
def update_status(request, operation, pk):
payment = Payment.objects.get(id=pk)
if operation == 'complete':
print("Many to many field print")
payment.status.first().type = 'complete'
payment.save()
return redirect('payment-detail', pk=pk)
return redirect('payment-detail', pk=pk)
I have tried multiple solutions from other posts but I keep getting many errors like Cannot edit many to many fields. Any insight or help would be very helpful because there doesn't seem to be much on the internet for this.

You need to save the Status object not the payment object.
def update_status(request, operation, pk):
payment = Payment.objects.get(id=pk)
if operation == 'complete':
print("Many to many field print")
status = payment.status.first()
status.type = 'complete'
status.save()
return redirect('payment-detail', pk=pk)
return redirect('payment-detail', pk=pk)
Also if you want to update all status objects related to payment this would also work payment.status.all().update(type='complete')

Related

Call a function in DetailView, views.py. which returns bool. Django, python

I am building an e-commerce website with django. There is a homepage which lists all items on website and when the user click on any of those items they will direct to details page which shows details about that item. Also on that detail page, I added a "wishlist" button so that users can add that item to their wishlist. My problem is, if the user does not have that item in their wishlist, I want to display "Add to wishlist" button to add that item to their wishlist when they clicked on it, otherwise I want to display "remove from wishlist" button to remove the item from their wishlist when they clicked on it. I have two separate function to add and remove items and they perfectly work but I want to display only one of the buttons "add" or "remove" by checking if that item exists in their wishlist or not. So my logic was writing a function checks if the user has that item in their wishlist or not in DetailView which displays the "detail" page in views.py and display only one of them but I couldn't.
This is views.py
class ItemDetailView(FormMixin, DetailView):
model = Auction
form_class = CommentForm
template_name = "auctions/details.html"
def dispatch(self, request, *args, **kwargs):
listing = get_object_or_404(Auction, pk=request.user.id)
if_exists = Wishlist.objects.filter(user=request.user, item=listing).exists()
return super().dispatch(request, if_exists, *args, **kwargs)
.
.
.
.
.
This is models.py
class Auction(models.Model):
title = models.CharField(max_length=64)
price = models.IntegerField()
created = models.DateTimeField(auto_now_add=True)
image = models.ImageField(null=True, blank=True, upload_to='images/')
category = models.ForeignKey(Category, on_delete=models.CASCADE, default="")
owner = models.ForeignKey(User, on_delete=models.CASCADE, default="")
description = models.TextField(max_length=256)
def __str__(self):
return f"({self.id}: {self.title} , {self.price}, {self.owner})"
def get_absolute_url(self):
return reverse('item-detail', args=[str(self.id)])
class Wishlist(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Auction, on_delete=models.CASCADE, null=True)
def __str__(self):
return f"{self.item}"
This is urls.py
path("item/<int:pk>", ItemDetailView.as_view(), name="item-detail"),
And I think the html should be something like this because the function should return True or False.
{% if not if_exists %}
</li style="display:inline-block">
Add To Wishlist
</ul>
{% else %}
</li style="display:inline-block">
Remove From Wishlist
</ul>
{% endif %}
So my logic was calling a function in DetailView when the user wants to see the details of items.
Result is there is no error but html doesn't show the button "add" when it should do so probably this func is returning None or False.(or not even working) but not True.
I am a newbie in software engineering so my code can be odd. I am open to any logics or suggestions. Thanks in advance.

Django - how add User specific Items?

Good day Stackoverflow,
a user should be able to add multiple titles instead of always overwriting the one added title.
\\ views.py
def edit_profile(request):
try:
profile = request.user.userprofile
except UserProfile.DoesNotExist:
profile = UserProfile(user=request.user)
if request.method == 'POST':
form = UserProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
return redirect('/test')
else:
form = UserProfileForm(instance=profile)
return render(request, 'forms.html', {'form': form, 'profile': profile})
\\models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
title = models.CharField(max_length=1024)
def __str__(self):
return str(self.title)
\\forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('title',)
Then the user has a form on the website where he can add the specific title.
Until now, however, every time the user fills out the title form, the value in the database is overwritten.
As it should be:
When a new title is added in the form, it should simply be added to it.
At the end I should have the possibility, with a Foor loop in the HTML template, to display all the added titles of the respective user.
Do you know how to do this?
If you are using a relational database, this functionality isn't really supported for a single field. Though, if you really wanted to, you could use a JSON field to make this work.
However, it is probably a better idea to use a separate table for titles.
To do this, you need to create a new Title object like:
class Title(models.Model):
Then, create a many-to-one relationship using ForeignKey:
class Title(models.Model):
text = models.CharField(max_length=1024)
user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
The on_delete method is required. This particular one will delete all Titles associated with a UserProfile if a UserProfile is deleted.
Now, if you want to associate a title object with a UserProfile, you would do it like this:
profile = UserProfile(user=request.user)
title = Title.objects.create(text='My Very First Title', user_profile=profile)
For more info on many-to-one relationships in Django: https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_one/
You can create new model and assign new with the ForeignKey field.
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return ', '.join([title for title in self.titles.all()])
class UserTitle(models.Model):
title = models.CharField(max_length=1024)
userprofile = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='titles')
def __str__(self):
return self.title
views.py:
def edit_profile(request):
...
if request.method == 'POST':
...
if form.is_valid():
form.instance.userprofile = request.user.userprofile
form.save()
return redirect('/test')
...
admin.py:
from django.contrib import admin
from your_app.models import UserProfile
class UserProfileAdmin(admin.ModelAdmin):
list_display = ['id', 'user', '__str__']
admin.site.register(UserProfile, UserProfileAdmin)
settings.py:
INSTALLED_APPS = [
...
'your_app',
...
In template, to make for loop just use:
{% for title in user.userprofile.titles.all %}
{{ title }}
{% endfor %}
or if you need only User titles in single string:
{{ user.userprofile }}

How to give data to ManyToManyField from view in django

Hello i am new to django and i am trying to create a page where we can add and participate in various events.
This is the model i created for my database
model.py
class Venue(models.Model):
name = models.CharField('Venue Name', max_length=120)
address = models.CharField(max_length=300)
zip_code = models.CharField('Zip Code', max_length=6)
phone = models.CharField('Contact Number', max_length=25, blank=True)
web = models.URLField('Website Address', blank=True)
email = models.EmailField('Email', blank=True)
owner = models.IntegerField('Venue Owner', blank=False, default=1)
venue_image = models.ImageField(null=True, blank=True, upload_to="images/")
class Event(models.Model):
name = models.CharField('Event Name', max_length=120)
event_date = models.DateTimeField('Event Date')
venue = models.ForeignKey(Venue, blank=True,null=True, on_delete=models.CASCADE)
manager = models.ForeignKey(User,blank=True,null=True,on_delete=models.SET_NULL)
description = models.TextField(blank=True, )
attendees = models.ManyToManyField(User,blank=True, related_name='attendees')
here i am trying to make a link by clicking that link user participate to that Event
but i am not getting how to put the user data in the above attendees field
view function
def attendees(request):
Event.attendees.add(request.user)
return redirect('list-events')
error : AttributeError at /participate/
'ManyToManyDescriptor' object has no attribute 'add'
link
Participate
url.py
path('participate/', attendees, name='participate')
You need to specify for which event the user will be added, so the view should look like:
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_POST
#require_POST
#login_required
def attendees(request, pk):
event = get_object_or_404(Event, pk=pk)
event.attendees.add(request.user)
return redirect('list-events')
participating should be done through a POST request since it alters entities. A GET request should only be used to retrieve data, not update it.
and the urls.py thus should contain a URL parameter for the primary key:
path('participate/<int:pk>/', attendees, name='participate')
Finally the template should thus make a POST request to the path with the primary key of the event:
<form method="post" action="{% url 'participate' pk=event.pk %}">
<button type="submit">Participate</button>
<form>
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: One can use the #require_POST decorator [Django-doc]
to restrict the view to only be accessible for a POST request.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Here is the easiest way of doing it. As I can understand Venue model is saved by the admin(i.e you). If not then you have to save the Venue first.
Now the key point is, in relationship fields, you have to pass the whole inheriting model object.
You can do something like this:
#in login view
def login(request):
#your code
request.session['userID']=userID
#your code
now you can use the session variable (i.e userID) in any view or template
#participate view
from mymodels import Event,Venue
def participate(request):
#your GET or POST parameters here
user=user_model.objects.get(id=request.session['userID'])
venue=Venue.objects.get(id=venue_id) #OR filter it by any other field like name
E1=Event()
E1.attendees= user
E1.venue= venue
E1.manager= user
E1.name= name_from_request_parameters
E1.description= description_from_request_parameters
E1.event_date= event_date_from_request_parameters
E1.save()
Tip: use default current date in datefield like
event_date = models.DateTimeField(default=datetime.now())

How to give multiple log-in's access to one keyword-based dataset in django app

Here is the scenario I am working on: I have django app that creates records which I call sessions:
blog.models.py
class Session(models.Model):
uid = models.CharField(max_length=50, blank=True)
cid = models.CharField(max_length=50, blank=True)
action_type = models.CharField(max_length=50, blank=True)
action_name = models.CharField(max_length=50, blank=True)
action_value = models.CharField(max_length=50, blank=True)
session_date = models.DateTimeField(auto_now_add=True)
client = models.CharField(max_length=50, blank=True)
I have a dashboard page to show charts and a database page to show the records as a table:
blog.urls.py
path('', auth_views.LoginView.as_view(template_name='users/login.html'), name='blog-home'),
path('<str:username>/dashboard/', views.dashboard and DashboardListView.as_view(), name='blog-dashboard'),
path('<str:username>/database/', views.database and SessionListView.as_view(), name='blog-database'),
So when you log in, my SessionListView.as_view() goes through the whole database and displays only those records where the Session.client == the url's 'username' value.
Example: when user: DummyCo logs in (www.website.com/DummyCo/database/) they see only Session records where the Session.client field is 'DummyCo.' This has worked out great so far.
But here is the problem: I now need to provide multiple logins to users to see the same dashboard and database page.
Example: jim#DummyCo.com and amy#DummyCo.com both need to see the DummyCo records, but if I provided them with their own logins then their username's in the url would not match and thus the DummyCo records would not show. I thought using the built-in django Groups would be a solution but that seems to only help with authentication and permissions on the backend. I also extended my user model with a Profile model:
users/models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
user_client = models.CharField(max_length=50, blank=True, null=True, default=None)
def __str__(self):
return f'{self.user.username} Profile'
I made the user_client model field to try and connect the Profile (and thus User) with the Session.client field: instead of <str:username>/database/ I thought i'd be able to use <str:client_user>/database/ and simply fill that field with 'DummyCo' on both Jim and Amy's profile to give them access to the records.
I read in a couple of places that the key to handling this problem is to switch the user model from one-to-one to many-to-one type early or before i build out the app. Unfortunately I have already put a ton of work into this project. I also read that I should look at the built-in User model as more of an account and less of a user. So is there a simple way to give multiple users access to one User/account?
Also, here is the views:
blog/views.py
class SessionListView(LoginRequiredMixin, ListView):
model = Session, Profile
template_name = 'blog/database.html'
context_object_name = 'sessions'
ordering = ['-session_date']
paginate_by = 25
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Session.objects.filter(client=user).order_by('-session_date')
def get_context_data(self, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
context = super().get_context_data(**kwargs)
context['distinct_campaigns'] = Session.objects.filter(client=user).values('cid').distinct().order_by('cid')
context['distinct_action_types'] = Session.objects.filter(client=user)\
.values('action_type')\
.distinct().order_by('action_type')
return context
# login_required()
def database(request):
context = {
'sessions': Session.objects.all()
}
return render(request, 'blog/database.html', context, {'title': 'Database'})
Okay I figured out a solution:
I thought I needed to do some trickery on the html file within the for loop showing my query set sessions but it turns out that can be adjusted in my views.py file. Before this update my views.py looked like this:
class SessionListView(LoginRequiredMixin, ListView):
model = Session, Profile
template_name = 'blog/database.html'
context_object_name = 'sessions'
ordering = ['-session_date']
paginate_by = 25
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Session.objects.filter(client=user).order_by('-session_date')
def get_context_data(self, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
context = super().get_context_data(**kwargs)
context['distinct_campaigns'] = Session.objects.filter(client=user).values('cid').distinct().order_by('cid')
context['distinct_action_types'] = Session.objects.filter(client=user)\
.values('action_type')\
.distinct().order_by('action_type')
return context
I realized the def get_queryset(self) was grabbing the logged-in username, then reviewing the full database and adding all records with the same session.client value as the value of the logged in user (i.e. DummyCo). So to make this work for a user like 'DummyCo_Sally', I changed the logic in that def like so:
class SessionListView(LoginRequiredMixin, ListView):
# gets the actual user (i.e. DummyCo_Sally)
user = get_object_or_404(User, username=self.kwargs.get('username'))
# turns user to a string
user_string = str(user)
# designates the _ as the separator
sep = '_'
# strips off _ and everything after it
stripped_user = user_string.split(sep, 1)[0]
# establishes the queryset as 'DummyCo' even though 'DummyCo_sally' is logged in
return Session.objects.filter(client=stripped_user).order_by('-session_date')
I doubt this method is the best way of handling multiple users seeing one umbrella data set, but it did the trick for me. This method also likely creates a security risk for applications that have public-facing user registration. But it did the trick for me.

Delete function in Django not working

I am trying to create a delete function for my Workout model.
This is the model:
class Workout(models.Model):
workoutID = models.AutoField(primary_key=True)
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def delete(self):
return reverse("delete_workout", kwargs = {'workout_id': self.workoutID})
Next I have the view:
def delete_workout(request, workout_id):
workout = get_object_or_404(Workout, workoutID = workout_id)
print(workout)
if request.user != workout.created_by:
return HttpResponse('Not ur workout')
else:
workout.delete()
return HttpResponseRedirect('/')
This is the url:
url(r'^(?P<workout_id>\d+)/delete/$', views.delete_workout, name='delete_workout'),
And finally the html:
<a href='{{ instance.delete }}'>
<button>Delete Workout</button>
</a>
I'm not getting any errors in the console, which is why I don't know what is going wrong.
You are overriding delete method of the class just for getting the delete url. You will get the url by url function in the template like {% url delete_workout instance.workoutID %}. So remove the delete function from the model change your html href url. Leave the view and url as the same. No issues there
class should be
class Workout(models.Model):
workoutID = models.AutoField(primary_key=True)
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
And your html should be
<a href='{% url delete_workout instance.workoutID %}'>
<button>Delete Workout</button>
</a>
NOTE: django model itself adds id for each table, so you dont have to specify it as you did workoutID = models.AutoField(primary_key=True).
By default each model will have a id field just like id = models.AutoField(primary_key=True)
If you consider removing the workoutID then the model becomes
class Workout(models.Model):
name = models.CharField(max_length=40)
created_by = models.ForeignKey(User)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
and the html will be
<a href='{% url delete_workout instance.id %}'>
<button>Delete Workout</button>
</a>
Django has all the tools for you under the hood. Don't reinvent the wheel. You can refactor and simplify your code.
First remove the method delete in Workout.
Second, replace your function-based-view with a class-based-view:
from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from django.http import Http404
from .models import Workout
class WorkoutDeleteView(DeleteView):
model = Workout
success_url = reverse_lazy('delete_workout')
def get_object(self):
obj = super().get_object()
if obj.created_by != self.request.user:
raise Http404
return obj
A workout can be deleted only by its author. In success_url you specify the target where the user should be redirected after deleting.
Just adapt slightly your urls.py (pay attention to the emphasised part):
url(r'^(?P<pk>\d+)/delete/$', views.WorkoutDeleteView.as_view(), name='delete_workout'),
EDIT:
You can name your views as you please, however it would be better to follow already well established conventions. Thus the names for the class based views should be workout-list, workout-detail, workout-create, workout-update and workout-delete.

Categories