[gae]redirect only succeed after second time - python

I am building a simple wiki on GAE, i wanted to add a normal redirect to the handler when the content put to the datastore.but it doesnt work until i submit twice it redirect to the content page not the edit page itself.
Here is my handler code:
class BlogHandler(webapp2.RequestHandler):
def write(self, *a, **kw):
self.response.out.write(*a, **kw)
def render_str(self, template, **params):
params['user'] = self.user
t = jinja_env.get_template(template)
return t.render(params)
def render(self, template, **kw):
self.write(self.render_str(template, **kw))
class WikiFront(BlogHandler):
def get(self, url):
#pages = WikiData.all()
pages = db.GqlQuery("SELECT * FROM WikiData")
found = False
content = ""
for page in pages:
if page.url == url:
found = True
content = page.content
if not found:
self.redirect("/_edit" + url )
else:
self.render('wiki.html', content=content, url=url)
class EditPage(BlogHandler):
def get(self,url):
if self.user:
self.render("wiki_edit.html")
else:
self.redirect("/login")
def post(self,url):
content = self.request.get('content')
if content:
w = WikiData(parent = wiki_key(), content = content, url= url)
w.put()
self.redirect('/wiki' + url )
else:
error = "Give us content plz"
self.render("wiki_edit.html", content=content, error=error)
# mapping stuff
app = webapp2.WSGIApplication([
('wiki' + PAGE_RE, WikiFront),
('/_edit' + PAGE_RE, EditPage),
],
debug=True)

If the redirect is happening after the Post method here, add a return statement after each redirect as it's more than likely trying to finish evaluating the method without completing the redirect.

Related

How to pass the fetched tweet(tweepy.StreamListener) into html page using django

I want to create a web page which should scrape twitter to fetch user data by given location
I am using tweepy's StreamListener to fetch tweet by location but i don't know how to pass that to html page using render in django
MyListerner class is fetching the tweets but its not returning the fetched tweet
I want to fetch tweets by given location and pass that into html file
when I tried to render the page inside MyStreamListener class I couldn't pass request value in on_status Error : undefined parameter request inside filter() method
Without passing request inside filter its raising on_status missing one argumet request and without defining request parameter in on_status i couldn't render the template Error : join() should not contain dict value
Is there any proper way to return the fetched tweet into django template
class Authenticator:
def authenticate_and_get_API_object(self):
apiKey = ""
apiSecretKey = ""
accessToken = ""
accessTokenSecret = ""
auth = tweepy.OAuthHandler(apiKey, apiSecretKey)
auth.set_access_token(accessToken, accessTokenSecret)
api = tweepy.API(auth)
return api
class MyStreamListener(StreamListener):
def __init__(self):
super().__init__()
self.start_time = time.time()
self.limit = 5
def on_status(self, status):
api = Authenticator().authenticate_and_get_API_object()
if (time.time() - self.start_time) < self.limit:
tweets = tweepy.Cursor(api.search, count=10, q="python", lang="en", since='2021-03-18').items(100)
count = 1
return tweets
else:
return False
def on_error(self, status_code):
if status_code == 420:
return False
class TweetScraper(View):
def get(self, request):
locForm = LocationForm()
args = {"locForm": locForm}
return render(request, 'tweetScraper/tweetscraper.html', args)
def post(self, request):
tweet_list = []
if request.method == 'POST':
locForm = LocationForm(request.POST or None)
formLoc = locForm.data
if 'listBox' in request.POST and 'secondlist' in request.POST:
location = formLoc['secondlist']
try:
# Specify the user_agent as your
# app name it should not be none
geolocator = Nominatim(user_agent="your_app_name")
loc = geolocator.geocode(location)
print(loc.latitude)
print(loc.longitude)
location = [loc.latitude,loc.longitude]
print(location)
the_api = Authenticator().authenticate_and_get_API_object()
myStream = Stream(auth=the_api.auth, listener=TweetScraper(), secure=True, )
myStream.filter(track=["BJP"])
myStream.filter(locations=location)
tweets = MyStreamListener.on_status()
except GeocoderTimedOut:
return 0
else:
False
locForm = LocationForm()
args = {"tweets":tweets,"locForm": locForm}
return render(request, 'tweetScraper/tweetscraper.html', args)

How can I pass a model id from the url to a class based view?

I have a class-based view:
class Create(View):
note_id = None
http_method_names = ['post', 'patch']
default_title = "You fool! This was left empty"
default_body = "Why did you leave this blank :("
def dispatch(self, *args, **kwargs):
method = self.request.POST.get('_method', '').lower()
print('method = ', method)
if method == 'patch':
return self.patch(*args, **kwargs)
elif method == 'post':
self.post(*args, **kwargs)
return super(Create, self).dispatch(*args, **kwargs)
def post(self, note_id):
date = datetime.date.today()
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note = Note(note_title=title, note_body=body, publish_date=date, edit_date=None)
note.save()
return HttpResponseRedirect(reverse('notes:note'))
def patch(self, note_id):
note = Note.objects.get(id=note_id)
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# if something changed
if title != note.note_title or body != note.note_body:
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note.note_title = title
note.note_body = body
note.edit_date = datetime.date.today()
note.save()
return HttpResponseRedirect(reverse('notes:note'))
and in url.py I have
urlpatterns = [
path('<int:note_id>/create/', views.Create.as_view(), name='create'),
path('<int:note_id>/edit/', views.Create.as_view(), name='edit')
]
Previously, with function-based views the note_id would just be passed to the function automatically. I can not figure out the equivalent of this in class based views. I've tried explictiely passing note_id to each function, but that did not work. I tried included Create.as_view(note_id=note_id) in my url.py, but to no avail.
Currently, running this code I get:
post() got multiple values for argument 'note_id'
As mentioned in the comment above, you need to access these values through self.kwargs. e.g:
class Create(View):
note_id = None # Delete this line
...
# delete the dispatch method - no need to overwrite this.
# Don't include note_id as an argument, but request should be an argument
def post(self, request):
note_id = self.kwargs['note_id']
....
def put(self, request): # Likewise here
note_id = self.kwargs['note_id']
....
It doesn't look like you need to write a custom dispatch method. If all you are doing is calling the appropriate method, then the dispatch method that comes for free with View is just fine :)

How to move some code from post method to a separate method in Django views

I have a class that take some info from a form, make some changes to it. And than saves it into database
At the moment all the logic is in the post method. And I want to make the code more structured and I want to put some part of it to a separate method. Is it possible? If so, how can I do it?
here is my code:
class AddSiteView(View):
form_class = AddSiteForm
template_name = 'home.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, { 'form': form })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
site_instanse = form.save()
url = request.POST.get('url', '')
if url.endswith('/'):
url = url + "robots.txt"
else:
url = url + "/robots.txt"
robot_link = Robot(
site = site_instanse,
link = url,
)
robot_link.save()
pk = Robot.objects.get(site=site_instanse)
return redirect('checks:robots', pk.id)
return render(request, self.template_name, { 'form': form })
I want to make 2 changes to it:
The 1st thing I want to do is to move this part of code to a separate method
if url.endswith('/'):
url = url + "robots.txt"
else:
url = url + "/robots.txt"
And the 2nd thing I want to do is to move this part of code also in a separate method
robot_link = Robot(
site = site_instanse,
link = url,
)
robot_link.save()
pk = Robot.objects.get(site=site_instanse)
return redirect('checks:robots', pk.id)
The reason is that I will be adding more functions here. And I don't want to have it all in post method. If it is possible, please, help me. I've already tried several ways of solving this problem, but they didn't work
Thank you
There is nothing special about Django preventing you from using plain python functions. So, if you know how to define methods and functions, you should take the same approach. For example, the first part can be the function
def get_robots_url(url):
if url.endswith('/'):
url = url + "robots.txt"
else:
url = url + "/robots.txt"
return url
Then you call the extracted function in the same place
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
site_instance = form.save()
url = request.POST.get('url', '')
url = get_robots_url(url)
....
You can also define a function inside the class - a method, to group the code. For the 2nd part:
class AddSiteView(View):
...
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
site_instanse = form.save()
url = request.POST.get('url', '')
url = get_robots_url(url)
return self.create_robot(site_instanse, url)
return render(request, self.template_name, { 'form': form })
def create_robot(self, site_instance, url):
robot_link = Robot(
site = site_instanse,
link = url,
)
robot_link.save()
pk = Robot.objects.get(site=site_instance)
return redirect('checks:robots', pk.id)

Django Pagination: switch between paginated/non-paginated ListView

I'm trying to elaborate a smart way to switch between a paginated template and a non-paginated one.
I already have a working paginator and I was thinking of adding a button next to it that read "Show all results" that linked to a non-paginated list, from there then there would be another button to go back to the paginated list.
1) Easy Solution
Use 2 ListViews with different assignations of the attribute paginate_by (django default to set pagination), but since I have many lists in my project it wouldn't be convenient (not much smart either).
2) Solution I'm stuck on
Write a Mixin (that will later be extended by my ListViews) to set the variable paginate_by based on a condition and then add some useful variables to the context :
class PaginationMixin:
no_pagination = False
no_pagination_url = ''
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
return self.no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
#...
def get_no_pagination_url(self):
return reverse('mylist_urlname')
PROBLEM: I don't know how to set the no_pagination variable from the template. Is there some way to do this?
Thanks for the help.
UPDATED SOLUTION (edited from #hi-lan solution):
This way it will show all results and also keep urlparams (from filters or other) if present.
class PaginationMixin:
toggle_pagination = False
toggle_pagination_url = ''
no_pagination = False
view_name = ''
urlparams_dict = {}
def get(self, request, page=None, *args, **kwargs):
#store current GET params and pop 'page' key
self.urlparams_dict = request.GET
self.urlparams_dict.pop('page', None)
page = page or request.GET.get('page', '1')
if page == 'all':
page = self.paginate_by = None
self.no_pagination = True
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_toggle_pagination_url(self):
# variables to set in view to toggle this mixin
if self.toggle_pagination and self.view_name:
if not self.no_pagination:
extra = {'page': 'all'}
self.urlparams_dict.update(extra)
else:
self.urlparams_dict.pop('page', None)
# url keeps track of urlparams adds page=all if toggled
self.toggle_pagination_url = reverse(self.view_name) + '?' + urlencode(self.urlparams_dict)
return self.toggle_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['toggle_pagination_url'] = self.get_toggle_pagination_url()
context['toggle_pagination'] = self.toggle_pagination
return context
The problem is at data flow back from user to indicate non pagination. The only way I can think of is to use special page number. There are two options, depends on which way you config urls.py.
In case of path('objects/page<int:page>/',
PaginatedView.as_view()), special number is 0 (as normal page number
is started from 1).
In case of /objects/?page=3, special number can be all.
In either case, we need to override get method as it is where we can retrieve user's selection.
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
# For using path
extra = {'page': '0'}
no_pagination_url = reverse(self.view_name, kwargs=extra)
# For using query params
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
view_name = 'mylist_urlname'
#...
I'm trying the UPDATED SOLUTION of gccallie with this view :
class StageTempList(PaginationMixin, LoginRequiredMixin, SingleTableMixin, FilterView):
view_name = 'stagetemp-list'
table_class = StageTempTable
model = StageTemp
filterset_class = StageTempFilter
template_name = 'stage/stagetemp_list.html'
paginate_by = 30
strict = False
But when get_paginate_by return None, I get 25 rows.
Django version 2.1.2
UPDATE : the PaginationMixin Class I use
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
self.no_pagination = True
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context

Cherrypy and content-type

I've got a cherrypy app and I'm trying to change response header Content-type. I'm trying to do that with cherrypy.response.header['Content-Type'] = 'text/plain'. Unfortunately I'm still getting 'text/html'. I want to have set one content type for ok request and another content type for error message. The only way how can I change content type is with my decorator. But this set type for the method and I need to change it. Do you know where could be a problem?
My config:
config = {
'/': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Content-Type', 'text/html')],
}
}
def GET(self, id):
cherrypy.response.headers['Content-Type'] = 'application/x-download'
somecode
if res < None:
cherrypy.response.headers['Content-Type'] = 'text/plain'
cherrypy.response.status=404
GET._cp_config = {'response.stream': True}
def stream():
def decorate(func):
def wrapper(*args, **kwargs):
name = time.strftime("%Y%m%d-%H%M%S")
cherrypy.response.headers['Content-Type'] = 'application/x-download'
cherrypy.response.headers['Content-Disposition'] = 'attachment; filename="' + name + '.txt"'
cherrypy.response.headers['Transfer-Encoding'] = 'chunked'
return func(*args, **kwargs)
return wrapper
return decorate
#cherrypy.expose
class Network:
#stream()
def GET(self, id):
source = my_generator()
for i in source:
if res < None:
cherrypy.response.headers['Content-Type'] = 'text/plain'
cherrypy.response.status=404
break
yield bytes(i)
GET._cp_config = {'response.stream': True}
Ok, there is more complex code, config of cherrypy is in the previous comment. I have a generator, which yields me some data and this is streaming that data in file to the client. I know, there are definitely better solutions. Imagine that in res variable there is result from saving to db. The problem is, that this completely ignores my settings in the if condition. It's still returning a file (empty one). The decorator is the only way how to set content type, that's why it's there

Categories

Resources