flask-admin handle request the right way - flask

how can i handle the request from listview template. if i click the submit button in my custom_list.html the the variables were not output as string. what did i do wrong ?
app.py
from flask import Flask
import flask_admin as admin
from flask_mongoengine import MongoEngine
from flask_admin.contrib.mongoengine import ModelView
from flask_admin.actions import action
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
app.config['MONGODB_SETTINGS'] = {'DB': 'test_app'}
# Create models
db = MongoEngine()
db.init_app(app)
class Website(db.Document):
domain_name = db.StringField(max_length=200)
title = db.StringField(max_length=160)
meta_desc = db.StringField()
class WebsiteView(ModelView):
list_template = 'custom_list.html'
#action('create_meta', 'Create Meta', 'Are you sure you want to create meta data?')
def action_createmeta(self, ids):
print "this is my domain_name {} and this is my title {}".format(
Website.domain_name, Website.title)
# Flask views
#app.route('/')
def index():
return 'Click me to get to Admin!'
if __name__ == '__main__':
# Create admin
admin = admin.Admin(app, 'Example: MongoEngine')
# Add views
admin.add_view(WebsiteView(Website))
# Start app
app.run(debug=True)
templates/custom_list.html
{% extends 'admin/model/list.html' %}
{% block body %}
<h1>Custom List View</h1>
{{ super() }}
{% endblock %}
{% block list_row_actions %}
{{ super() }}
<form class="icon" method="POST" action="/admin/website/action/">
<input id="action" name="action" value="create_meta" type="hidden">
<input name="rowid" value="{{ get_pk_value(row) }}" type="hidden">
<button onclick="return confirm('Are you sure you want to create meta data?');" title="Create Meta">
<span class="fa fa-ok icon-ok"></span>
</button>
</form>
{% endblock %}
output

You are printing the definitions of Website.domain_name not the values.
From the documentation and example you need to do:
for id in ids:
found_object = Website.objects.get(id=id)
print "this is my domain_name {} and this is my title {}".format(
found_object.domain_name, found_object.title)
EDIT: original post.
Change the line
print "this is my domain_name {} and this is my title {}".format(
Website.domain_name, Website.title)
to
print "this is my domain_name {} and this is my title {}".format(
Website.domain_name.data, Website.title.data)
You are printing the objects not the posted data contained within them

Related

why first parameter is send as id to views in django [duplicate]

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.

How do I trigger a Celery task from Django admin?

I have a model called Publication and I'd like to add a button to the list view in Django Admin which would allow triggering a Celery task.
admin.py:
from django.contrib import admin
from .models import Publication
class PublicationAdmin(admin.ModelAdmin):
change_list_template = "variants/admin_publication_list.html"
def update(self, request):
# trigger task
# redirect to admin list
admin.site.register(Publication, PublicationAdmin)
variants/admin_publication_list.html:
{% extends 'admin/change_list.html' %}
{% block object-tools %}
<li>
<a href="/admin/variants/publication/update/">
Update
</a>
</li>
{{ block.super }}
{% endblock %}
However when I press a button I only get a notice:
Publication with ID “update” doesn’t exist. Perhaps it was deleted?
Method name update or route name update is too generic and sometimes used by frameworks automatically, try naming it to match your functionality. revoke-publications or sync-publications.
admin.py:
from django.contrib import admin
from django.urls import path
from django.http import HttpResponseRedirect
from .models import Publication
from .tasks import your_celery_task
#admin.register(Publication)
class PublicationAdmin(admin.ModelAdmin):
change_list_template = "variants/admin_publication_list.html"
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('update-publications/', self.publication_update),
]
return my_urls + urls
def publication_update(self, request):
result = your_celery_task.delay("some_arg")
self.message_user(
request,
f"Task with the ID: {result.task_id} was added to queue. Last known status: {result.status}"
)
return HttpResponseRedirect("../")
variants/admin_publication_list.html:
{% extends 'admin/change_list.html' %}
{% block object-tools %}
<div>
<form action="update-publications/" method="POST">
{% csrf_token %}
<button type="submit">Run Celery Task</button>
</form>
</div>
<br />
{{ block.super }}
{% endblock %}

View not showing when using modelform

I'm trying to display a form, using a model form in django. Everything looks like it's setup properly, and I'm not getting any error. Simply, the form is not showing, although the url is updated...
views.py
from rulz.models import Rulz
class rules_create(CreateView):
model = Rulz
fields=['title', 'content']
models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Rulz(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
country = models.CharField(max_length=255,default='France')
city = models.CharField(max_length=255,default='Paris')
player_num = models.IntegerField(default=2)
complexity = models.IntegerField(default=1)
created_on = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User,on_delete=models.CASCADE,default=1)
def get_absolute_url(self):
return reverse('rulz:rulz_detail',kwargs={'pk':self.pk})
urls.py (in the app)
app_name = 'rulz'
urlpatterns = [
#/rulz/
url(r'^', views.rules_index.as_view(), name='rulz_index'),
url(r'^index/$', views.rules_index.as_view(), name='rulz_index'),
# /rulz/details
url(r'^(?P<pk>[0-9]+)/$',views.rules_detail.as_view(),name='rulz_detail'),
#rulz/create
url(r'^create/',views.rules_create.as_view(),name='rulz_create'),
]
urls.py (root folder)
...
url(r'^rules/',include('rulz.urls')),
...
app/templates/app/rulz_form.html
{% extends 'rulz/Rulz_base.html' %}
{% block body %}
{% load staticfiles %}
{% include 'rulz/form-template.html' %}
{% endblock %}
app/templates/app/form-template.html
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.errors }}</span>
</div>
<div class="validate-input m-b-26" >
<label class="label-input100">{{ field.label_tag }}</label>
<div class="input100">{{ field }}</div>
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.help_text }}</span>
</div>
</div>
</div>
and finally the button with the link in my page to access the form :
Go create Rule
I really don't know what I am missing. When I click this button, the url is uploaded to http://127.0.0.1:8000/rules/create/ but the displayed page is exactly the same.
Any clue ?
Thanks
You need to terminate your index regex. At the moment it matches every string the has a start, which of course means every string. Use the $:
url(r'^$', views.rules_index.as_view(), ...
(As an aside, you should avoid having two patterns for the same view.)
In the documentation you can find the list of the attributes needed to correctly rendering your form using CreateView.
So, you need to modify your class based view to look like at least this example:
from rulz.models import Rulz
class rules_create(CreateView):
form_class = YOUR_FORM # The form that will be used with this class
model = Rulz
fields=['title', 'content']
success_url = 'YOUR_SUCCESS_URL' # If success redirect to this URL

DatePickerWidget with Flask, Flask-Admin and WTforms

I'm trying to render a template that contains a DatePicker, but I'm getting a 500 error when I try. For my the code is correct, but it seems that something is failing or I'm not understanding correctly the way to do it.
The code is the following:
Reporting.py
from flask.ext.admin import BaseView, expose
from wtforms import DateField, Form
from wtforms.validators import Required
from flask.ext.admin.form import widgets
from flask import request
class DateRangeForm(Form):
start_date = DateField('Start', validators=[Required()], format = '%d/%m/%Y', description = 'Time that the event will occur', widget=widgets.DatePickerWidget)
class ReportingView(BaseView):
#expose('/')
def index(self):
form = DateRangeForm(request.form)
return self.render('reporting.j2', form=form)
Reporting template:
{% extends 'admin/master.html' %}
{% block body %}
{{super()}}
Working on it!
{% if form %}
{{form.start_date}}
{% endif %}
{% endblock %}
As davidism says in the comments, the default DateField just provides date parsing, it'll just be displayed as a normal text-input.
If you're ready to fully embrace html5 then you can use the DateField from wtforms.fields.html5 which will render a datepicker in any browser that supports it:
from flask import Flask, render_template
from flask_wtf import Form
from wtforms.fields.html5 import DateField
app = Flask(__name__)
app.secret_key = 'SHH!'
class ExampleForm(Form):
dt = DateField('DatePicker', format='%Y-%m-%d')
#app.route('/', methods=['POST','GET'])
def hello_world():
form = ExampleForm()
if form.validate_on_submit():
return form.dt.data.strftime('%Y-%m-%d')
return render_template('example.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
The more usual approach is to find a nice datepicker online, that's using CSS and JS to handle it's display and include that code into your html template, and tell it to apply the datepicker style to any html element that has a class of datepicker. Then when you generate your form you can just do:
<!-- all your CSS and JS code, including the stuff -->
<!-- to handle the datepicker formatting -->
<form action="#" method="post">
{{ form.dt(class='datepicker') }}
{{ form.hidden_tag() }}
<input type="submit"/>
</form>
Any attributes (that aren't reserved for other use) you pass into rendering the form element will by default just add it as an attribute, for example the {{ form.dt(class='datepicker') }} will generate <input class="datepicker" id="dt" name="dt" type="text" value=""> so your CSS/JS can then do whatever it needs to do to provide a good interface for your user.

Flask wtform forms.validate_on_submit() is always false

I have looked at other similar problems on here and a few other places, but the solutions don't seem to help with my problem. Even though, I am not seeing too much of a difference between this simple code that I've got and other similar code. Especially this one Flask - wtforms: Validation always false
forms.validate_on_submit() is always false and I can't see why.
I'm going through the Flask Web Development Book by Miguel Grinberg, but I wanted to change some things in order to learn more.
It works when using the wtf.quick_form(form) in the html template, but if I remove the quickform entry and put in form fields , then it doesn't work
The screen just refreshes and it doesn't change Stranger to whatever name is entered
HTML index template
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
<form action="" method='POST'>
{{ form.name.label }} <br>
{{ form.name }}
{{ form.submit }}
</form>
{% endblock %}
relevant code hello.py
from flask import Flask, render_template, request
from flask.ext.script import Manager
from flask.ext.bootstrap import Bootstrap
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField, RadioField, TextField, validators
from wtforms.validators import Required
from wtforms.validators import DataRequired
app = Flask(__name__)
class NameForm(Form):
name = StringField('What is your name?',validators=[Required()] )
submit = SubmitField('Submit')
#app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm(request.form) #From the docs I read I don't need
# request.form but it
# doesn't work either with it or without it
if form.validate() == True:
name='True' #never happens is not validating or is always set to False for
# some reason
if form.validate_on_submit(): #isn't validating or working
name = form.name.data #'Stranger' will disappear from the html template and
#replaced with the name the user entered in the
# Stringfield
form.name.data = '' #clear stringfield for next round
return render_template('index.html',form=form, name=name)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=True)'
what am I not understanding\missing?
Thanks
g
The problem is with wtf not finding the CSRF Tokens as part of your form data. Add {{ form.hidden_tag() }} or {{ form.csrf_token }} as the top element of your form.
Just a small remind for anyone who uses bootstrap template form like me.
Be sure to add "name" attribute into the input tag as well. For example,
<label>Your name</label>
<input name = 'name' required>
<label>Your email</label>
<input name = 'email' required>