I have a Django project where I extended the User to have a Profile using a OneToOneField. I'm using CBV UpdateView which allows users to update their profile. The URL they visit for this is ../profile/user/update. The issue I have is that if a user types in another users name they can edit the other persons profile. How can I restrict the UpdateView so the authenticated user can only update their profile. I was trying to do something to make sure user.get_username == profile.user but having no luck.
Models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.core.urlresolvers import reverse
class Profile(models.Model):
# This field is required.
SYSTEM_CHOICES = (
('Xbox', 'Xbox'),
('PS4', 'PS4'),
)
system = models.CharField(max_length=5,
choices=SYSTEM_CHOICES,
default='Xbox')
user = models.OneToOneField(User)
slug = models.SlugField(max_length=50)
gamertag = models.CharField("Gamertag", max_length=50, blank=True)
f_name = models.CharField("First Name", max_length=50, blank=True)
l_name = models.CharField("Last Name", max_length=50, blank=True)
twitter = models.CharField("Twitter Handle", max_length=50, blank=True)
video = models.CharField("YouTube URL", max_length=50, default='JhBAc6DYiys', help_text="Only the extension!", blank=True)
mugshot = models.ImageField(upload_to='mugshot', blank=True)
def __unicode__(self):
return u'%s' % (self.user)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance, slug=instance)
post_save.connect(create_user_profile, sender=User)
def get_absolute_url(self):
return reverse('profile-detail', kwargs={'slug': self.slug})
Views.py
from django.shortcuts import render
from django.views.generic import DetailView
from django.views.generic.edit import UpdateView
from django.views.generic.list import ListView
from profiles.models import Profile
class ProfileDetail(DetailView):
model = Profile
def get_context_data(self, **kwargs):
context = super(ProfileDetail, self).get_context_data(**kwargs)
return context
class ProfileList(ListView):
model = Profile
queryset = Profile.objects.all()[:3]
def get_context_data(self, **kwargs):
context = super(ProfileList, self).get_context_data(**kwargs)
return context
class ProfileUpdate(UpdateView):
model = Profile
fields = ['gamertag', 'system', 'f_name', 'l_name', 'twitter', 'video', 'mugshot']
template_name_suffix = '_update'
def get_context_data(self, **kwargs):
context = super(ProfileUpdate, self).get_context_data(**kwargs)
return context
Admin.py
from django.contrib import admin
from models import Profile
class ProfileAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('user',), }
admin.site.register(Profile, ProfileAdmin)
Urls.py for Profiles app
from django.conf.urls import patterns, url
from django.contrib.auth.decorators import login_required
from profiles.views import ProfileDetail, ProfileUpdate
urlpatterns = patterns('',
url(r'^(?P<slug>[-_\w]+)/$', login_required(ProfileDetail.as_view()), name='profile-detail'),
url(r'^(?P<slug>[-_\w]+)/update/$', login_required(ProfileUpdate.as_view()), name='profile-update'),
)
Profile_update.html
{% extends "base.html" %} {% load bootstrap %}
{% block content %}
{% if user.is_authenticated %}
<h1>Update your profile</h1>
<div class="col-sm-4 col-sm-offset-4">
<div class="alert alert-info alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>Heads up!</strong> Other users can find you easier if you have a completed profile.
</div>
<form enctype="multipart/form-data" method="post" action="">{% csrf_token %}
{{ form|bootstrap }}
<input class="btn btn-default" type="submit" value="Update" />
</form>
</div>
{% else %}
<h1>You can't update someone elses profile.</h1>
{% endif %}
{% endblock %}
How about something like this:
from django.contrib.auth.views import redirect_to_login
class ProfileUpdate(UpdateView):
[...]
def user_passes_test(self, request):
if request.user.is_authenticated():
self.object = self.get_object()
return self.object.user == request.user
return False
def dispatch(self, request, *args, **kwargs):
if not self.user_passes_test(request):
return redirect_to_login(request.get_full_path())
return super(ProfileUpdate, self).dispatch(
request, *args, **kwargs)
In this example, the user is redirected to default LOGIN_URL. But you can easily change it . to redirect user to their own profile.
To avoid access to data unrelated to the connected user when using Class Based View (CBV), you can use Dynamic filtering and define queryset instead on model attributes.
If you have a book.models with a ForeignKey (named user here) on auth.models.user you can easily restrict acces like this :
# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from books.models import Book
class BookList(LoginRequiredMixin, ListView):
def get_queryset(self):
return Book.objects.filter(user=self.request.user)
See more explanation in the documentation about CBV - Viewing subsets of objects
Specifying model = Publisher is really just shorthand for saying queryset = Publisher.objects.all(). However, by using queryset to define a filtered list of objects you can be more specific about the objects that will be visible in the view.
[…]
Handily, the ListView has a get_queryset() method we can override. Previously, it has just been returning the value of the queryset attribute, but now we can add more logic.
The key part to making this work is that when class-based views are called, various useful things are stored on self; as well as the request (self.request) this includes the positional (self.args) and name-based (self.kwargs) arguments captured according to the URLconf.
your template.html:
{% if request.user.is_authenticated and profile.user == request.user %}
your form
{% else %}
u cannot edit that profile - its not yours...
{% endif %}
Related
Can someone help me to solve this error?
ValueError at /create_entry/
Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x000000B7BBF1BFC8>>": "Entry.entry_author" must be a "User" instance.
urls.py
from django.urls import path
from .views import HomeView, EntryView, CreateEntryView
urlpatterns = [
path('', HomeView.as_view(), name = 'blog-home'),
path('entry/<int:pk>/', EntryView.as_view(), name = 'entry-detail'),
path('create_entry/', CreateEntryView.as_view(success_url='/'), name = 'create_entry')
]
views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView
from .models import Entry
class HomeView(ListView):
model = Entry
template_name = 'entries/index.html'
context_object_name = "blog_entries"
class EntryView(DetailView):
model = Entry
template_name = 'entries/entry_detail.html'
class CreateEntryView(CreateView):
model = Entry
template_name = 'entries/create_entry.html'
fields = ['entry_title', 'entry_text']
def form_valid(self,form):
form.instance.entry_author = self.request.user
return super().form_valid(form)
models.py
from django.db import models
from django.contrib.auth.models import User
class Entry(models.Model):
entry_title=models.CharField(max_length=50)
entry_text=models.TextField()
entry_date=models.DateTimeField(auto_now_add=True)
entry_author=models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "entries"
def __str__(self):
return f'{self.entry_title}'
create_entry.html
{% extends "entries/base.html" %}
{% block content %}
<div class="col-md-8"><br><br>
<!-- Blog Post -->
<div class="card mb-4">
<div class="card-header">
Create Blog Post
</div>
<div class="card-body">
<form class="form-conrol" action="" method="post">
{% csrf_token %}
{{form.as_p}}
<button type="submit" class="btn btn-primary">Post Entry</button>
</form>
</div>
</div>
</div>
{% endblock %}
I need your help for this
small project.
You are not logged in, so self.request.user is not a real user. You can use the LoginRequiredMixin [Django-doc] to restrict access to a view such that you can only post (and retrieve) the view when the user has logged in:
from django.contrib.auth.mixins import LoginRequiredMixin
class CreateEntryView(LoginRequiredMixin, CreateView):
model = Entry
template_name = 'entries/create_entry.html'
fields = ['entry_title', 'entry_text']
def form_valid(self,form):
form.instance.entry_author = self.request.user
return super().form_valid(form)
I would like to return a very basic, single paragraph from a model but I don't know how or which is the best approach. It's a simple description textField (maindescription) that I would like to be able to change in the future instead of hard-coding it. It has to be simple way but I just could find a good example. Would appreciate any help on how to properly write the view and retrieve it in the template.
model.py
from autoslug import AutoSlugField
from model_utils.models import TimeStampedModel
from time import strftime, gmtime
# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
class Song(models.Model):
author = models.CharField("Author", max_length=255)
song_name = models.CharField("Song Name", max_length=255)
slug = AutoSlugField("Soundtrack", unique=True, always_update=False, populate_from="song_name")
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
audio_file = models.FileField(upload_to='mp3s/', blank=True)
def __str__(self):
return self.song_name
class MainDescription(models.Model):
main_description = models.TextField()
slug = AutoSlugField("Main Description", unique=True, always_update=False, populate_from="main_description")
def __str__(self):
return self.main_description
view.py
from django.views.generic import ListView, DetailView
from .models import Song, MainDescription
class SongListView(ListView):
model = Song
# Overwrite the default get_context_data function
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add extra information here, like the first MainDescription Object
context['main_description'] = MainDescription.objects.first()
return context
admin.py
from django.contrib import admin
from .models import Song, MainDescription
admin.site.register(Song)
admin.site.register(MainDescription)
urls.py
from django.urls import path
from . import views
app_name = "music"
urlpatterns = [
path(
route='',
view=views.SongListView.as_view(),
name='list'
),
]
song_list.html
{% extends 'base.html' %}
{% block content %}
<div class="container">
<p>{{ main_description.main_description }}</p>
</div>
<ul class="playlist show" id="playlist">
{% for song in song_list %}
<li audioURL="{{ song.audio_file.url }}" artist="{{ song.author }}"> {{ song.song_name }}</li>
{% endfor %}
</ul>
{% endblock content %}
</div>
It looks like you're trying to add extra context to the SongListView
class SongListView(ListView):
model = Song
# Overwrite the default get_context_data function
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add extra information here, like the first MainDescription Object
context['main_description'] = MainDescription.objects.first()
return context
Then in your template you could do something like this
<p>{{ main_description.main_description }}</p>
For more information about that from the docs, you can find it here
Im trying to implement a chat-function for my website. In order to do that, i followed the following tutorial: https://channels.readthedocs.io/en/latest/tutorial/
I've then changed the code a little bit in order to implement it. Until here, everything works just fine. Now I want to store the form-data inside of a database, and thats where the problem appears.
But first my code:
urls.py:
from django.urls import path
from .views import ChatOverView
urlpatterns = [
path('<int:pk>/', ChatOverView.as_view(), name='chat-explicit'),
path('', ChatOverView.as_view(), name='chat-home'),
]
views.py (theres much code here that is probably not needed for this question, but since i dont know what part of it i can ignore, im just posting the whole file-content):
from django.views.generic import ListView
from django.views.generic.edit import FormMixin, FormView
from django.db.models import Q
from django.urls import resolve
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from .models import Message
from .forms import MessageRegisterForm
class ChatOverView(ListView, FormMixin):
model = Message
template_name = 'chat/home-chat.html'
form_class = MessageRegisterForm
success_url = '/thanks/'
def form_valid(self, form):
form = self.get_form()
if form.is_valid():
data = form.cleaned_data
return super().form_valid(form)
def get_context_data(self, *args, **kwargs):
context = super(ChatOverView, self).get_context_data(*args, **kwargs)
messages_raw = reversed(Message.objects.filter(Q(sender=self.request.user) | Q(receiver=self.request.user)))
messages = {}
for mes in messages_raw:
# i am receiver
if mes.sender != self.request.user:
if mes.sender in messages:
messages[mes.sender].append({"received": mes})
else:
messages.update({mes.sender: [{"received": mes}]})
# i sent
else:
if mes.receiver in messages:
messages[mes.receiver].append({"sent": mes})
else:
messages.update({mes.receiver: [{"sent": mes}]})
active_user = self.get_active_chat(messages)
chatroom_name = self.get_chatroom_name(active_user)
context.update(messages_data=messages, active=active_user, roomname=chatroom_name)
return context
def get_chatroom_name(self, active_chat):
# my convention
ids = [active_chat.id, self.request.user.id]
ids.sort()
return str(ids[0]) + '_' + str(ids[1])
def get_active_chat(self, messages):
url_name = resolve(self.request.path_info).url_name
if url_name == "chat-home":
return list(messages.keys())[0]
else:
pk_user = self.request.build_absolute_uri().split("/")[-2]
user = get_object_or_404(User, pk=pk_user)
return user
home-chat.html:
{% extends "solawi/base.html" %}
{% load define_dictfilters %}
{% block content %}
<form class="bg-light" method="post">
<div class="input-group">
{% csrf_token %}
{{ form }}
<div class="input-group-append">
<button type="submit" id="chat-message-submit" value="enter">send</button>
</div>
</div>
</form>
{% endblock content %}
models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Message(models.Model):
sender = models.ForeignKey(User, related_name="sender", on_delete=models.CASCADE)
receiver = models.ForeignKey(User, related_name="receiver", on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.sender.username + ' to ' + self.receiver.username
The rest of the files/settings entrys are the same as in the tutorial linked above.
Now comes the problem: when submitting the form, i get the following error:
Method Not Allowed (POST): /chat/
Method Not Allowed: /chat/
HTTP POST /chat/ 405 [0.00, 127.0.0.1:54424]
How can i fix it?
Thank you for your Help!!
ListView implements a get() method, but no post() method. You need to implement a post() method in order for the view to allow POST requests.
You could subclass django.views.generic.ProcessFormView to get this, or, if you really need a ListView, then you can add a post() method to the class which handles the form validation and whatever else you need to do. Here is how ProcessFormView implements it:
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
I'm creating a simple blog application. A user is logged in this application while He/She can comment any post on my blog application. But cant impletement that idea.
This is my models.py file
from django.db import models
# Create your models here.
from user.models import CustomUser
from django.conf import settings
from django.db import models
from django.urls import reverse
class BlogPost(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
blog_title=models.CharField(max_length=200)
blog_description=models.TextField()
blog_pub=models.DateTimeField(auto_now_add=True)
blog_update=models.DateTimeField(auto_now=True)
def __str__(self):
return self.blog_title
def get_absolute_url(self):
return reverse('blog:blog_post', kwargs={'pk': self.pk})
class Comment(models.Model):
blogpost=models.ForeignKey(BlogPost, on_delete=models.CASCADE)
comment=models.CharField(max_length=300)
author=models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,blank=True, null=True)
author_name = models.CharField(max_length=50, default='anonymous', verbose_name=("user name"))
comment_pub = models.DateTimeField(auto_now_add=True)
comment_update = models.DateTimeField(auto_now=True)
def get_absolute_url(self):
return reverse('blog:home', kwargs={'pk':self.pk})
def __str__(self):
return self.comment
This is views.py file
class BlogPostSingle(DetailView, FormView):
model=BlogPost
template_name='blog/blog_post_single.html'
#fields = ['blog_title']
form_class = CreateCommentForm
success_url = '/blog/'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
this is my forms.py file
class CreateCommentForm(ModelForm):
class Meta:
model=Comment
fields = ('comment', 'blogpost')
and this is my html file and forms section
{% if user.is_authenticated %}
<h5>Hi, {{user.name}} leave your comment now</h5>
<form action="" method="post">
{% csrf_token %} {{form.as_p}}
<input type="submit" value="Submit comment">
</form>
{% else %}
<p>You're not logged in this site, please log in for comment </p>
{% endif %}
My target Idea: Just user logged on my blog application. He can be able to comment any post on my blog application. And my Comment Models contain two forignkey field.
You should pass the user to your view's context, so it will be available in the template:
class BlogPostSingle(DetailView, FormView):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
on get_context_data see https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-display/#detailview
on self.request see
https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/#dynamic-filtering
Hello I have problem with saving forms to database. When I try to save the AdHistoryForm in ads_history_add view the forim is rendered correctly but after submitting nothing happens aside of redirecting me to ads_history_list view.
In addition when I try to submit this form with empty field it doesnt show any errors (I included them in template), so maybe it is validation thing.
When I try to add Ad in ads_add view everything is ok.
Can you help me?
models.py
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class Ad(models.Model):
title = models.CharField(max_length=128, verbose_name=_("name"), help_text=_("required"), unique=True)
content = models.TextField(verbose_name=_("content"), blank=True)
url = models.URLField(verbose_name=_("website"), blank=True)
date_create = models.DateTimeField(auto_now_add=True)
date_modify = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.title
class AdHistory(models.Model):
ad = models.ForeignKey(Ad)
user = models.ForeignKey(User)
comment = models.TextField(verbose_name=_("comment"), help_text=_("required"))
date_create = models.DateTimeField(auto_now_add=True)
date_modify = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.comment
forms.py
from django import forms
from .models import Ad, AdHistory
class AdForm(forms.ModelForm):
class Meta:
model = Ad
fields = ['title', 'content', 'url']
class AdHistoryForm(forms.ModelForm):
class Meta:
model = AdHistory
fields = ['comment']
views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required, user_passes_test
from django.utils.translation import ugettext as _
from .models import Ad, AdHistory
from .forms import AdForm, AdHistoryForm
#login_required
#user_passes_test(lambda u: u.is_superuser)
def ads_list(request):
ads_list = Ad.objects.all().order_by('-date_modify')
context = {'list': ads_list}
return render(request, 'ads_list.html', context)
#login_required
#user_passes_test(lambda u: u.is_superuser)
def ads_add(request):
form = AdForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('ads_list')
context = {'form': form}
return render(request, 'ads_form_add.html', context)
#login_required
#user_passes_test(lambda u: u.is_superuser)
def ads_history_list(request, ad_id):
ad = get_object_or_404(Ad, pk=ad_id)
history_list = AdHistory.objects.select_related().filter(ad=ad).order_by('-id')
context = {'list': history_list, 'object': ad}
return render(request, 'ads_history_list.html', context)
#login_required
#user_passes_test(lambda u: u.is_superuser)
def ads_history_add(request, ad_id):
ad = get_object_or_404(Ad, pk=ad_id)
f = AdHistoryForm(request.POST or None)
if f.is_valid():
new_entry = f.save(commit=False)
new_entry.ad = ad
new_entry.user = request.user
new_entry.save()
return redirect('ads_history_list', ad_id)
context = {'form': f, 'object': ad}
return render(request, 'ads_history_add.html', context)
urls.py
rom django.conf.urls import patterns, url
from django.contrib.auth.decorators import login_required
from ads import views
urlpatterns = patterns(
'',
url(r'^$', views.ads_list, name="ads_list"),
url(r'^add/', views.ads_add, name="ads_add"),
url(r'^(?P<ad_id>\d+)/history/$', views.ads_history_list, name="ads_history_list"),
url(r'^(?P<ad_id>\d+)/history/add$', views.ads_history_add, name="ads_history_add"),
)
both form templates inherits from this template:
<form role="form" method="post" action=".">
{% csrf_token %}
<table class="table table-bordered crm-form">
{% for field in form.visible_fields %}
<tr>
<th>
{{ field.label }}
</th>
<td>
{{ field }}
<small>{{ field.help_text }}</small>
{% if field.errors %}
<div class="alert alert-danger" role="alert">{{ field.errors }}</div>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<button type="submit" name="submit" class="btn btn-success crm-float-right">
{% trans 'Save' %}
</button>
</form>
The POST request never reaches your ads_history_add view because your ads_history_add URL pattern does not have a trailing slash. Without the trailing slash, action="." in the ads_form_add.html template results in a POST to (?P<ad_id>\d+)/history/
Add the trailing slash and everything should work as expected. Alternatively, you could omit the action attribute to tell the browser to POST to the current URL.
Also note that, although not relevant here, it is probably a good habit to display {{ form.non_field_errors }}.