I'm making a messaging system which has a message model and some views for things like inbox and conversation.
Inbox view and template works and is linked to the conversation through the id of the sender. However, when I click on the senders name (The senders name is the link (template link is {{ sender }}) that is meant to trigger the conversation view which is supposed to render the template convo.html ( the project level url for that is url(r'^messaging/inbox/conversation/(?P<id>[\w-]+)/$', message_views.Conversation.as_view(), name="conversation"),) ), Django changes the url but stays on the same page(So it triggers the inbox view and stays on inbox.html and changes the url but doesn't trigger the conversation view and doesn't render the convo.html template). I've tried changing from a function based view to a class based view and it did nothing. What am I doing wrong?
Note: I'm still new to django so forgive me for any bad practices; I'm attempting to teach myself.
views.py (inbox and conversation views)
def inbox(request):
messages = Message.objects.filter(receiver=request.user.id)
senders = {}
for message in messages:
if message.sender not in senders:
senders[message.sender] = message
return render(request, 'inbox.html', {'senders': senders})
# There is only ever one of this view, either the CBV or FBV
def conversation(request, other_user_id):
print(request.method)
if request.method == "POST":
reply = request.post['reply']
other_user = CustomUser.objects.get(id=other_user_id)
message = Message(sender=request.user, receiver=other_user)
message.save()
return redirect("conversation")
else:
other_user = CustomUser.objects.get(id=other_user_id)
messages = Message.objects.filter(Q(receiver=request.user) & Q(sender=other_user))
print(messages)
return render(request, 'convo.html', {'messages': messages})
# Class based conversation view
class Conversation(View):
def post(request, other_user_id):
reply = request.post['reply']
other_user = CustomUser.objects.get(id=other_user_id)
message = Message(sender=request.user, receiver=other_user)
message.save()
return redirect("conversation")
def get(request, other_user_id):
other_user = CustomUser.objects.get(id=other_user_id)
messages = Message.objects.filter(Q(receiver=request.user) & Q(sender=other_user))
print(messages)
return render(request, 'convo.html', {'messages': messages})
template - inbox.html
<!DOCTYPE html>
{% extends "accbase.html" %}
<!--Omitted outbox in favor of conversation-->
{% block content %}
<div class="inbox">
<h1>Inbox</h1>
<hr>
{% for sender in senders %}
{{ sender }}
<br>
{{sender.message.created_at}}
<br>
{% endfor %}
</div>
{% endblock %}
template - convo.html
<!DOCTYPE html>
<!--Template for reciever side of conversation-->
{% extends "accbase.html"%}
{% block content %}
<div class="conversation">
<h1>Conversation with {{sender}}</h1>
<p>
{% for msg in messages %}
<!--Order is descending ~~ bottom to top: newest to oldest-->
{{ msg.content }}
<br>
{% endfor %}
<hr>
<form method="POST">
<label>Reply</label>
<input type="text" name="reply">
</form>
</p>
<button type="submit">Send</button>
</div>
{% endblock %}
project level urls.py
from django.urls import path, re_path, include
from django.conf.urls import url
from messaging import views as message_views
urlpatterns = [
url(r'^messaging/inbox', message_views.inbox, name="inbox"),
url(r'^messaging/inbox/conversation/(?P<id>[\w-]+)/$', message_views.Conversation.as_view(), name="conversation"),
]
As Daniel Roseman said in comments " The problem here is that you didn't terminate the regex for your inbox URL, so it also matches the conversation URL. " so I added the needed $ to the end of the inbox url pattern and that fixed it!
Related
I'm trying to clone the Instagram web page using Django(version-3.1).
My Django project has an app called 'post'.
One of its template I have a form which is posting a comment to a post. The form post request should call the path('add_comment/',views.add_comment,name='add_comment'), but It's calling path('<slug:slug>/',views.post_details,name='post_details'), instead. And raising DoesNotExist at /post/add_comment error. I added print() statement at the beginning of both add_comment() and post_details() methods to find out which is running when the request is made. I have no idea what I have done wrong.
The project GitHub link - https://github.com/mirasel/Instagram_Clone
the post_details.html template is -
{% extends 'base.html' %}
{% load static %}
{% block title %} post {% endblock %}
{% block profilephoto %} {{ propic.url }} {% endblock %}
{% block body %}
<div>
<div>
<img src="{{post.image.url}}" alt="post" height="250px" width="250px">
</div>
<div>
<a href="{% url 'instagram:profile' post.uploader %}">
<img src="{{uploader.profile_pic.url}}" alt="{{uploader}}" style="border-radius: 50%;" height="24px" width="24px">
{{ post.uploader }}
</a><br>
<p>{{ post.date_published.date }}</p>
</div>
<div>
<p>{{ post.caption }}</p>
</div>
<div>
<form action="{% url 'post:add_comment' %}" id="comment_form" method="POST">
{% csrf_token %}
<textarea name="comment" id="comment" cols="30" rows="1" placeholder="Write a comment..."></textarea>
<input type="hidden" name="slug" id="slug" value="{{post.slug}}">
<!-- <input type="submit" style="display: none;" name="submit"> -->
</form>
<script>
$(function(){
$("#comment").keypress(function (e) {
if(e.which == 13 && !e.shiftKey) {
$(this).closest("form").submit();
e.preventDefault();
}
});
});
</script>
{% endblock %}
the views.py -
from django.shortcuts import render,redirect
from instagram.views import get_nav_propic,get_profile_details
from .models import UserPost,PostComment,PostLike
from django.http import JsonResponse
def get_post_likes(post):
likes = PostLike.objects.filter(post=post)
total_likes = len(likes)
likers = []
for l in likes:
likers.append(get_profile_details(l.liker))
return {'likers':likers,'total_likes':total_likes}
def get_post_comments(post):
comments = PostComment.objects.filter(post=post)
total_comments = len(comments)
commenter = []
comment = []
for c in comments:
commenter.append(get_profile_details(c.commenter))
comment.append(c.comment)
postcomment = zip(commenter,comment)
return {'post_comment':postcomment,'total_comments':total_comments}
def upload_post(request):
if request.method == 'POST':
image = request.FILES['post_img']
caption = request.POST['caption']
uploader = request.user
UserPost.objects.create(uploader=uploader,image=image,caption=caption)
return redirect('instagram:feed')
else:
context = {
'propic' : get_nav_propic(request.user)
}
return render(request,'post/upload_post.html',context)
def post_details(request,slug):
print('I am here in post details')
post = UserPost.objects.get(slug=slug)
context = {
'propic' : get_nav_propic(request.user),
'post' : post,
'uploader' : get_profile_details(post.uploader),
'LIKES' : get_post_likes(post),
'COMMENTS' : get_post_comments(post),
}
return render(request,'post/post_details.html',context)
def add_comment(request):
print('I am here in add comment')
if request.method == 'POST':
post_slug = request.POST.get('slug')
post = UserPost.objects.get(slug=post_slug)
user = request.user
comment = request.POST.get('comment')
PostComment.objects.create(post=post,commenter=user,comment=comment)
return redirect('post:post_details',slug=post_slug)
the urls.py -
from django.urls import path
from . import views
app_name='post'
urlpatterns = [
path('upload_post/',views.upload_post,name='upload_post'),
path('<slug:slug>/',views.post_details,name='post_details'),
path('add_comment/',views.add_comment,name='add_comment'),
]
The error - Error page
Solved
I had to make the URL path of add_comment as following-
#previous one
path('add_comment/',views.add_comment,name='add_comment'),
#modified one
path('comment/add_comment/',views.add_comment,name='add_comment'),
This is because the pattern for the slug URL and add comment URL were similar.
Because Django will process the urlpatterns sequentially, from docs:
Django runs through each URL pattern, in order, and stops at the first
one that matches the requested URL, matching against path_info.
And '/add_comment' is a valid slug <slug:slug>, so post_details will be called.
So you should keep the definition of the most generic url patterns at last:
urlpatterns = [
path('upload_post/',views.upload_post,name='upload_post'),
path('add_comment/',views.add_comment,name='add_comment'),
path('<slug:slug>/',views.post_details,name='post_details'),
]
Hopefully this will work for you.
Writing an admin action so an administrator can select a template they can use to send a message to subscribers by inputting only the subject and text message. Using a filtered list from the admin panel an action called broadcast is triggered on this queryset (the default filter list). The admin action 'broadcast' is a function of a sub-classed UserAdmin class. The intermediate page is displayed that shows a dropdown selector for the emailtype, the queryset items (which will be email addresses, input fields for the subject and message text (message is required field) a button for optional file attachment followed by send or cancel buttons. Problem 1) after hitting the send button the app reverts to the admin change list page. In the broadcast function, the conditional if 'send' in request.POST: is never called.
forms.py
mail_types=(('1','Newsletter Link'),('2','Update Alert'))
class SendEmailForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
#Initialized 'accounts' from Account:admin.py Actions: 'send_email' using>> form = SendEmailForm(initial={'accounts': queryset})
my_mail_type=forms.ChoiceField(label='Mail Type',choices=mail_types,required=False)
subject = forms.CharField(widget=forms.TextInput(attrs={'placeholder': ('Subject')}),required=False)
message = forms.CharField(widget=forms.Textarea(attrs={'placeholder': ('Teaser')}),required=True,min_length=5,max_length=1000)
attachment = forms.FileField(widget=forms.ClearableFileInput(),required=False)
accounts = forms.ModelChoiceField(label="To:",
queryset=Account.objects.all(),
widget=forms.SelectMultiple(attrs={'placeholder': ('user_email#somewhere.com')}),
empty_label='user_email#somewhere.com',
required=False,
admin.py
from .forms import SendEmailForm
from django.http import HttpResponseRedirect,HttpResponse
from django.shortcuts import render, redirect
from django.template.response import TemplateResponse
def broadcast(self, request, queryset):
form=None
if 'send' in request.POST:
print('DEBUGGING: send found in post request')
form = SendEmailForm(request.POST, request.FILES,initial={'accounts': queryset,})
if form.is_valid():
#do email sending stuff here
print('DEBUGGING form.valid ====>>> BROADCASTING TO:',queryset)
#num_sent=send_mail('test subject2', 'test message2','From Team',['dummy#hotmail.com'],fail_silently=False, html_message='email_simple_nb_template.html',)
self.message_user(request, "Broadcasting of %s messages has been started" % len(queryset))
print('DEBUGGING: returning to success page')
return HttpResponseRedirect(request, 'success.html', {})
if not form:
# intermediate page right here
print('DEBUGGING: broadcast ELSE called')
form = SendEmailForm(request.POST, request.FILES, initial={'accounts': queryset,})
return TemplateResponse(request, "send_email.html",context={'accounts': queryset, 'form': form},)
send_email.html
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static %}
{% load crispy_forms_tags %}
{% block content %}
<form method="POST" enctype="multipart/form-data" action="" >
{% csrf_token %}
<div>
<div>
<p>{{ form.my_mail_type.label_tag }}</p>
<p>{{ form.my_mail_type }}</p>
</div>
<div>
<p>{{ form.accounts.label_tag }}</p>
<p>
{% for account in form.accounts.queryset %}
{{ account.email }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
<p><select name="accounts" multiple style="display: form.accounts.email">
{% for account in form.accounts.initial %}
<option value="{{ account.email }}" selected>{{ account }}</option>
{% endfor %}
</p></select>
</div>
<div>
<p>{{ form.subject.label_tag }}</p>
<p>{{ form.subject }}</p>
</div>
<div>
<p>{{ form.message.label_tag }}</p>
<p>{{ form.message }}</p>
</div>
<div>
<p>{{ form.attachment.label_tag }}</p>
<p>{{ form.attachment.errors }}</p>
<p>{{ form.attachment }}</p>
</div>
<input type="hidden" name="action" value="send_email" />
<input type="submit" name="send" id="send" value="{% trans 'Send messages' %}"/>
{% trans "Cancel this Message" %}
</div>
</form>
{% endblock %}
Inspecting the browser at the POST call seems to show all the data was bound. Another poster here suggested the admin action buttons divert requests to an internal 'view' and you should redirect to a new view to handle the POST request. I can't get that to work because I can't get a redirect to 'forward' the queryset. The form used in the suggested fix was simpler and did not use the queryset the same way. I have tried writing some FBVs in Forms.py and Views.py and also tried CBVs in views.py but had issues having a required field (message) causing non-field errors and resulting in an invalid form. I tried overriding these by writing def \_clean_form(self): that would ignore this error, which did what it was told to do but resulted in the form essentially being bound and validated without any inputs so the intermediate page didn't appear. Which means the rabbit hole returned to the same place. The send button gets ignored in either case of FBVs or CBVs, which comes back to the admin action buttons Post requests revert to the admin channels!
Any ideas on how to work around this? Key requirements: From the admin changelist action buttons:
the Form on an intermediate page must appear with the queryset passed from the admin changelist filter.
The message input field on the form is a required field.
the send button on the HTML form view needs to trigger further action.
NOTES: My custom Admin User is a subclass of AbstractBaseUser called Account, where I chose not to have a username and am using USERNAME_FIELD='email'. Also, I do not need a Model.py for the SendEmailForm as I don't need to save the data or update the user models, just send the input message using the chosen template and queryset. Help is much appreciated!
It will never work in your case:
You call the action.
You receive the Action Confirmation template render.
After pressing "SEND" in your "confirmation" step, you send a POST request to ModelAdmin, not in your FB-Action.
ModelAdmin gets a POST request without special parameters and shows you a list_view by default.
In your case, you should add a send_email.html template:
{% load l10n %}
{# any your staff here #}
{% block content %}
<form method="POST" enctype="multipart/form-data">
{# any your staff here #}
<div>
<p>{{ form.attachment.label_tag }}</p>
<p>{{ form.attachment.errors }}</p>
<p>{{ form.attachment }}</p>
</div>
{% for obj in accounts %}
<input type="hidden" name="_selected_action" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="broadcast" />
{# any your staff here #}
</form>
{% endblock %}
You should change your action view, some things are not working in your code:
def broadcast(self, request, queryset):
form = SendEmailForm(data=request.POST, files=request.FILES, initial={'accounts': queryset})
if 'send' in request.POST:
... # your staff here
if form.is_valid():
... # your staff here
# return HttpResponseRedirect(request, 'success.html', {} ) this is NEVER WORK
return TemplateResponse(request, 'success.html', {})
... # your staff here
return TemplateResponse(request, "send_email.html",context={'accounts': queryset, 'form': form},)
I am giving you a solution that I have TESTED on my project. I am sure, it works.
We were told on DjangoCon Europe 2022 that django-GCBV is like a ModelAdminAction and I've added a link below for the talk.
https://youtu.be/HJfPkbzcCJQ?t=1739
I can't get that to work because I can't get a redirect to 'forward' the queryset
I have a similar use case and save the primary keys of the filtered query set in the session (in your case you may be able to save emails and avoid another query)
def broadcast(self, request, queryset):
request.session["emails"] = list(queryset.values_list("emails", flat=True))
return HttpResponseRedirect("url_to_new_view")
I can then use primary keys to filter query set in the new view. You also handle the form in this new view.
User.objects.filter(email__in=self.request.session["emails"])
I would like to have an alert() message (like in javascript) after method in view.py is complete
My method is
def change_password(request):
dictData = getInitialVariable(request)
in_username = request.POST['txt_username']
in_password = request.POST['txt_password']
in_new_password = request.POST['txt_new_password']
user = authenticate(username=in_username, password=in_password)
if user is not None:
if user.is_active:
u = User.objects.get(username=in_username)
u.set_password(in_new_password)
u.save()
# Redirect to a success page.
return HttpResponseRedirect('/profiles/'+in_username)
After u is saved to database, the popup message will be shown.
How could I implement it?
I think the best solution would be messages (docs)
As described in message levels docs Django suggests to use "INFO" level messages to communicate with users.
By default messages are enabled in Django. If my example doesn't work for you as it is you should check enable messages block
View part:
from django.contrib import messages
def change_password(request):
...your stuff...
messages.info(request, 'Your password has been changed successfully!')
return HttpResponseRedirect('/profiles/'+in_username)
Template part:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
You can paste massage output in specific view or in general templates (layout/header).
There are many ways to do this well (see "flash" in Bootstrap, for example)... but here's how you do literally what you ask about.
In the view you redirect to, pass a message value to your template:
return render_to_response('template_name', message='Save complete')
And in your template, add this script:
<script>
alert('{{ message }}');
</script>
add the messages.success after the send_mail()
from django.contrib import messages
def contact(request):
subject = request.POST['name']
message = request.POST['message']
recipient = settings.EMAIL_HOST_USER
recipient = [recipient,]
email_from = request.POST['mailing']
send_mail( subject, message, email_from, recipient )
messages.success(request, 'Successfully Sent The Message!')
return redirect('send_email')
add this code in your templates in body
{% if messages %}
{% for message in messages %}
{% if message.tags %} <script>alert("{{ message }}")</script> {% endif %}
{% endfor %}
{% endif %}
Simply redirect to /profiles/in_username/password_updated instead
you could use a HTML template on /profiles/in_username/password_updated:
This template redefines the one used in /profiles/in_username and add a javascript with alert
<!DOCTYPE html>
<html lang="en">
<body>
<script>
alert("Your message");
</script>
[...]
</body>
</html>
Simply use render_to_response in that view:
from django.shortcuts import render_to_response
return render_to_response('mytemplate.html', {"in_username": in_username});
See https://docs.djangoproject.com/en/1.7/topics/templates/ for more info on templating.
The following problem is occurring in a large django project. I've been able to replicate the issue in a small mock-up project (code below).
I am trying to use the django messaging framework within an inclusion tag to display a message when a POST'ed form returns is_valid(). This approach has also been used in an another answer here (see 'final update' section).
The problem is that the message is not immediately displayed when the page is rendered after the POST. Instead the message appears the next time you navigate elsewhere or refresh the page after the POST response is received.
I am not receiving any errors. Everything appears to be operating normally, except for the delayed message display.
The reason for this approach is because I'm reusing multiple small forms across multiple apps and I need to use DRY principals for the GET and POST logic. This approach works perfectly - except for the issue with the delayed 'success' message display!
Really appreciate any feedback or assistance!
EDIT: To be clear the line which sets the message is in 'my_template.py':
messages.add_message(context['request'], messages.SUCCESS, "Successfully added entry")
The Demo Project:
settings.py:
...
TEMPLATE_CONTEXT_PROCESSORS = (
"django.core.context_processors.request",
"django.core.context_processors.media",
"django.contrib.messages.context_processors.messages"
)
...
base_layout.html:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
{% for message in messages %}<div class="alert{% if message.tags %} alert-{{ message.tags }}{% endif %}" role="alert">{{ message }}</div>{% endfor %}
{% block content %}{% endblock %}
</body>
</html>
my_template.html:
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
forms.py:
from django.forms.models import ModelForm
from app.models import ContactMessage
class ContactForm(ModelForm):
class Meta:
model = ContactMessage
fields = ['name']
index.html:
{% extends "app/base_layout.html" %}
{% load my_template %}
{% block content %}
{% my_template %}
{% endblock %}
my_template.py:
from django import template
from django.contrib import messages
from app.forms import ContactForm
register = template.Library()
#register.inclusion_tag('app/my_template.html', takes_context=True)
def my_template(context):
if context['request'].method=='GET':
return { 'form':ContactForm() }
if context['request'].method=='POST':
form = ContactForm(context['request'].POST)
if not form.is_valid():
return { 'form': form }
form.save()
messages.add_message(context['request'], messages.SUCCESS, "Successfully added entry")
return { 'form':ContactForm() }
The messaging framework works through middleware, what you need is some way of informing the posting in the same request/response cycle. You have the context variable at hand, so why not add a value to it:
if form.is_valid():
context['success']=True
else:
context['success']=False
Then in your template:
{%if success %}<div>whoohoo!</div>{%endif%}
According to Django, the messages are queued for rendering until it is cleared by renderer. (reference)
In your case, you are adding messages after {{ message }} tags in base.html has been rendered. So your message is stored until your next view when {{ message }} in base.html is rendered again.
To solve this, you can move your {{ message }} tags behind {% endblock %} of content. Another possible solution is to use javascript to append {{ message }} tags either from my_template.html or from end of base.html.
I'm kind of puzzled with this task:
I have 2 tables: User, Codes
I want to generate randomly codes in a specific pattern.
I've already written that part as a function, but it's hard to implement the function
in the ModelAdmin.
So I would be very pleased if someone knows a trick to accomplish this.
It would be enough to have a button in the User form to envoke the function, which then creates these codes.
But how do I implement such a button?
Is there a way to to this?
EDIT: typo
SOLUTION:
Since I want to generate vouchers for a particular user I can edit the admin.py like this:
class MyUserAdmin(UserAdmin):
def vouchers(self, obj):
return "<a href='%s'>Generate vouchers</a>" % reverse(gen_voucher_view, kwargs={'user':obj.pk,})
vouchers.allow_tags = True
list_display = (..., 'vouchers')
which represents a clickable link in the admin view of my User model.
Now I connect the link to my view in urls.py by adding
url(r'admin/gen_vouchers/(?P<user>\w+)/$', gen_voucher_view, name='gen_voucher_view')
to urlpatterns.
For creating the vouchers I provide a form in forms.py
class VoucherGeneratorForm(forms.Form):
user = forms.CharField(User, required=True, widget=forms.HiddenInput())
amount = forms.IntegerField(min_value=0, max_value=500, required=True)
readonly = ('user', )
In views.py I'm adding my view function:
#login_required
def gen_voucher_view(request, user):
if request.method == 'POST': # If the form has been submitted...
form = VoucherGeneratorForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# GENERATE vouchers here by using form.cleaned_data['amount']
# and user (generate_vouchers is a self defined function)
vouchers = generate_vouchers(user, form.cleaned_data['amount']
# set error or info message
if len(vouchers) == form.cleaned_data['amount']:
messages.info(request, "Successfully generated %d voucher codes for %s" % (form.cleaned_data['amount'], user))
else:
messages.error(request, "Something went wrong")
u = User.objects.get(pk=user)
form = VoucherGeneratorForm(initial={'user':user}) # An unbound form
return render_to_response('admin/codes.html', {'request': request, 'user':user, 'form':form, 'userobj': u}, context_instance=RequestContext(request))
else:
form = VoucherGeneratorForm(initial={'user':user}) # An unbound form
Last but not least create a template admin/codes.html where my form is displayed:
{% extends "admin/base_site.html" %}
{% load i18n admin_static static %}
{% block breadcrumbs %}
<div class="breadcrumbs">
{% trans 'Home' %}
›
{% trans 'Users' %}
›
{% trans 'Vouchercodes' %}
›
Voucher Generator
</div>
{% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />{% endblock %}
{% block content %}
<div id="content-main">
{% if request.user.is_active and request.user.is_staff or userobj and userobj.is_active and userobj.is_staff %}
<h1 id="generator_title">Generate vouchers for {{user}}</h1>
<form id="formular_generator" action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<table>{{ form }}</table>
<button id="generatebutton" type="submit" name="action" value="generate">Generate</input>
</form>
{% else %}
<p>{% trans "You don't have permission to access this site." %}</p>
</div>
{% endif %}
{% endblock %}
{% block sidebar %}
{% endblock %}
Done!
To export them in a pdf I used admin actions, as propsed by Sumeet Dhariwal below.
U mean that you need to run a script from within the admin ?
If so check out django-admin-tools
http://django-admin-tools.readthedocs.org/en/latest/dashboard.html
SOLUTION FOUND:
no that was not what i meant, because I want to generate vouchers for 1 particular user and not for more, but that's a good remark.