How to "save and continue editing" a task in django-viewflow - django

I'm building an approval workflow using django-viewflow and django-material.
A key component of the workflow is for the end user to fill in a lengthy form and eventually submit it for review and approval.
What I want
My users will expect to be able to save and continue editing, so that they can fill in the form half way, save their progress, and come back later to complete the form filling. Only then they'll want to submit the form.
This is a standard feature in the Django admin.
What I've found
In the default task.html template, django-viewflow offers two submit buttons (source):
<button type="submit" name="_continue" class="btn btn-flat">{% trans 'Done and continue on this process' %}</button>
<button type="submit" name="_done" class="btn primary white-text">{% trans 'Done' %}</button>
The official docs are pretty slim on the difference between these two tasks.
"Done and continue" marks the current task as done and activates the next task (which as a user I want to do by default, other than "save and continue editing").
"Done" seems to mark the current task as done, but doesn't proceed to the next task, which requires some awkward self-assigning to proceed. I can see how a user can get confused here.
What I've tried
I've read the viewflow documentation, especially on activation but couldn't find any explanation on _continue vs _done.
Searching the code bases of django-viewflow (incl frontend) and django-material, I found the flow.html template. _continue is referenced from tasks.py and utils.py. _done is not referenced anywhere I could find.
flow/view/utils.py (source) seems to point to the next task (if any assigned to user) on completion of a task.
I can see a viewflow GH issue from 2016 on this
providing an example mixin (credits GH #codingjoe):
class SavableViewMixin:
#Activation.status.transition(source=STATUS.PREPARED, target=STATUS.ASSIGNED)
def save_task(self):
self.task.save()
def activation_done(self, *args, **kwargs):
if '_save' in self.request.POST:
self.save_task()
else:
self.done()
def message_complete(self):
if '_save' not in self.request.POST:
super(SavableViewMixin, self).message_complete()
def get_success_url(self):
if '_save' in self.request.POST:
return self.request.get_full_path()
return super(SavableViewMixin, self).get_success_url()
This snippet doesn't work anymore, which is expected since it's referencing a six years old viewflow version. I would need to dig into the Activation/Task API to figure out exactly what to override.
The issue also references a now removed example "viewflow_pause" in the cookbook.
Where I'm stuck
Is there a canonical way to provide a "save and continue editing" button to each task?
Is the "viewflow_pause" example still around somewhere?
If I have to reinvent this myself, what should I override?

Related

Plugging fundamental security vulerabilities in a Django web app

I have a Django app which users congregate to, use as a forum and gain reputation points. Majority of the users belong to underserved communities and use primitive, non-js feature phones with proxy browsers such as Opera mini, over low bandwidth internet. Essentially I'm a "Next Billion" digital non-profit.
There are some security vulnerabilities that users can exploit in my forum - I need advice in plugging those. Here are the facts.
There's no SSL certificate installed - all comm. takes place over HTTP. To post in the forum one uses the following piece of code in the django template:
<form action="{% url 'private_group_reply' slug=unique %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="unique" value="{{ unique }}">
<br><span style="color:green;">Image: </span>{{ form.image }}<br>
<br><span style="color:green;">Comment:</span>{{ form.text }}
<br>
<input class="button" type="submit" value="OK" id="id_submit">
</form>
unique is a uuid that identifies which group the comment is to be posted in.
The relevant url pattern is: url(r'^group/(?P<slug>[\w.#+-]+)/private/$', auth(PrivateGroupView.as_view()), name='private_group_reply')
In views.py, the relevant class-based view's method is:
def form_valid(self, form):
if self.request.user_banned:
return redirect("profile", slug=self.request.user.username)
else:
f = form.save(commit=False)
text = f.text
if text == self.request.user.userprofile.previous_retort:
redirect(self.request.META.get('HTTP_REFERER')+"#sectionJ")
else:
self.request.user.userprofile.previous_retort = text
self.request.user.userprofile.score = self.request.user.userprofile.score + 2
self.request.user.userprofile.save()
#print "image: %s" % f.image
if f.image:
image_file = clean_image_file(f.image)
if image_file:
f.image = image_file
else:
f.image = None
else:
f.image = None
#print "image:%s" % f.image
which_group = Group.objects.get(unique=self.request.POST.get("unique"))
reply = Reply.objects.create(writer=self.request.user, which_group=which_group, text=text, image=f.image)
GroupSeen.objects.create(seen_user= self.request.user,which_reply=reply)
try:
return redirect(self.request.META.get('HTTP_REFERER')+"#sectionJ")
except:
return redirect("private_group_reply", slug=reply.which_group.unique)
One particular user is, I think, running a script bot and flooding the group he's a part of, garnering multiple points (each posting is point-incentivized).
Though I'm new to software development, I now have a thriving community on this forum and am thus ramping up my skill-set. I've been reading a ton about SSL security, and rules of thumb any respectable web app ought to follow. What someone can help with currently is this particular case of the flooding user I've described, how to curtail such behavior within my set up. Also any general guidelines to follow will be highly appreciated as well.
Thanks in advance, and please ask for any information you need.
I'm not sure if its the answer you're looking for but rather than trying to patch up this one part of your code, you probably need to look into creating some rules/regulations on your site along with penalties for the users that break this.
If you can prove that a user is doing something against your rules/guidelines, you apply your set penalty (score rollback/bans/whatever) and this would remove the incentive for most to even try doing this.
Yes, ssl may help but who knows, and your users will always have the chance to bypass your safeguards.
In terms of the actual code? I wouldn't save the score to the model until everything else has been done. You might also want to make it an atomic transaction to roll back db changes if something goes awry when saving

Submit Button Confusion and Request being sent Twice (Using Flask)

I'm pretty much trying to create a web app that takes 2 svn urls and does something with them.
The code for my form is simple, I'm also using WTForms
class SVN_Path(Form):
svn_url=StringField('SVN_Path',[validators.URL()])
I'm trying to create 2 forms with 2 submit buttons that submit the 2 urls individually so my test3.html looks like this:
<form action="" method="post" name="SVNPath1">
{{form1.hidden_tag()}}
<p>
SVN Directory:
{{form1.svn_url(size=50)}}
<input type="submit" value="Update">
<br>
{% for error in form1.svn_url.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}
</p>
</form>
<form action="" method="post" name="SVNPath2">
{{form2.hidden_tag()}}
<p>
SVN Directory:
{{form2.svn_url(size=50)}}
<input type="submit" value="Update">
<br>
{% for error in form2.svn_url.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}
</p>
</form>
MY FIRST QUESTION is how do I know which submit button was clicked so I can run the proper function on the corresponding svn url. I have tried doing something like
if request.form1['submit'] == 'Update':
if request.form2['submit'] == 'Update':
but that does not work at all. I'm new to web dev in general and flask so a detailed explanation would be helpful.
SECONDLY, since submits weren't working properly I also tried an alternative to keep my work moving so in my .py file I have
#app.route('/test3', methods=['GET','POST'])
def test3():
basepath=createDir()
form1=SVN_Path()
form2=SVN_Path()
if request.method=="POST":
if form1.validate_on_submit():
svn_url = form1.svn_url.data
prev_pdf=PDF_List(svn_url,basepath,'prev') #some function
if form2.validate_on_submit():
svn_url2 = form2.svn_url.data
new_pdf=PDF_List(svn_url,basepath,'new') #some function
return render_template('test3.html', form1=form1, form2=form2)
CreateDir is a function that creates a directory in the local /tmp using timestamps of the local time.
Whenever I go the webpage it creates a directory, lets call it dir1, since its calling CreateDir. Thats what I want, but when I click submit on the form it creates another directory dir2 in the tmp folder which is NOT what I want since I want everything to being the same dir1 directory.
In addition when I put a url in one of the forms and click submit, it automatically puts it the same value in the 2nd form as well.
Sorry if this is really long and possibly confusing, but any help is appreciated.
:) Let's see if we can clarify this a little.
To your first question:
As #dim suggested in his comment, You have a few options:
You can submit your form to separate unique urls. That way you know which form was submitted
You can create two similar but different Form classes (the fields will need different names like prev_svn_url and cur_svn_url). This way in your view function, you instantiate two different forms and you'll know which form was submitted based on form.validate_on_submit()
The third option would be to add a name attribute to your submit button and then change the value attributes to something like 'Update Previous' and 'Update Current'. This way in your view function you can check the value of request.data[<submit button name>] to determine if 'Update Previous' was pressed or 'Update Current'.
To your second question:
Multiple directories are being created because you're calling createDir() each time the page is loaded to show the forms and when the forms get posted. In order to create just once, you'll need some kind of logic to determine that the directory was not previously created before calling createDir()
In addition: Since both forms are from the same SVN_Path class, they read post data exactly the same way, that's why whatever you type in form 1 appears in form 2.
Now for my 2 cents:
I assume you're trying to write some kind of application that takes two SVN urls as input, creates a folder and does something with those URLs in that folder. If this is the case, the way you are currently going about it is inefficient and won't work well. You can achieve this with just one form class having 2 svn_url fields (with different names of course) and then handling all of that in one post.
EDIT: The job of the submit button is to tell the browser that you're ready to send the data on the form to the server. In this case you should only need one submit button (SubmitFiled => when rendered). Clicking that one submit button will send data from both input fields to your view function.
Your form should look something like:
class SVN_Path(Form):
prev_svn_url=StringField('Previous SVN_Path',[validators.URL()])
new_svn_url=StringField('New SVN_Path',[validators.URL()])
and your view function:
def test():
form = SVN_Path()
if request.method == "POST":
if form.validate_on_submit():
basepath = createDir() # Only create dir when everything validates
prev_svn_url = form.prev_svn_url.data
new_svn_url = form.new_svn_url.data
prev_pdf = PDF_List(prev_svn_url, basepath, 'prev')
new_pdf = PDF_List(new_svn_url, basepath, 'new')
...
return render_template('test3.html', form1=form1, form2=form2)

Django redirect page does not update the view

I'm using the Django Framework on Google App Engine.
I have multiple forms on the same view, to submit to different URL.
Trouble is after I get a form submitted: even if the called method update the datastore and some data, the previous page (where the forms are put in) is not refreshed, showing the updated data.
I could solve this problem using jQuery or some javascrip framework, appending dinamically content returned by the server but, how to avoid it?
Suggestions?
Am I wrong somewhere?
A part of "secure.html" template
<form action="/addMatch" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
Matches:
<br />
{% for m in matches%}
{{m.description}} ---> {{m.reward}}
{% endfor%}
the "/addMatch" URL view:
def addMatch(request):
form = MatchForm(request.POST)
if form.is_valid():
user = User.all().filter('facebookId =', int(request.session["pbusr"]))
m = Match(user=user.get(),description =form.cleaned_data["description"],reward=form.cleaned_data["reward"])
m.save()
return HttpResponseRedirect("/secure/")
else:
logging.info("Not valid")
return HttpResponseRedirect("/secure")
The view method whose seems not working:
#auth_check_is_admin
def secure(request):
model={}
user = User.all().filter('facebookId =', int(request.session["pbusr"]))
u = user.get()
if (u.facebookFanPageId is not None and not u.facebookFanPageId == ""):
model["fanPageName"] = u.facebookFanPageName
model["form"] = MatchForm()
model["matches"] = u.matches
else:
....
return render(request,"secure.html",model)
Francesco
Based on what you posted, it seems like you're redirecting properly and are having database consistency issues. One way to test this would be to look at the network tab in the Google Chrome developer tools:
Click on the menu icon in the upper right
Click on "Tools"
Click on "Developer Tools"
Click on "Network" in the thing that opened up at the bottom of the screen.
Now, there will be a new entry in the network tab for every request that your browser sends and every response it receives. If you click on a request, you can see the data that was sent and received. If you need to see requests across different pages, you might want to check the "Preserve log" box.
With the network tab open, go to your page and submit the form. By looking at the network tab, you should be able to tell whether or not your browser issued a new GET request to the same URL. If there is a new request for the same page but that request has the old content, then you have a datastore consistency issue. If there was NOT a new request that yielded a response with the data for the page, then you have a redirect issue.
If it turns out that you have a datastore consistency issue, then what's happening is the data is being stored, but the next request for that data might still get the old data. To make sure that doesn't happen, you need what's called "strong consistency."
In a normal App Engine project, you get strong consistency by putting entities in the same entity-group and using ancestor queries. I'm not certain of what database/datastore you're using for Django and how the different database layers interact with App Engine's consistency, so this could be wrong, but if you can give your users the right key and then fetch them from that key directly (rather than getting all users and filtering them by key), you might get strong consistency.

Django/Haystack - best option to have a listview with search capability

I have an app with a Restaurant model. I'd like to understand what is the best way to put together a view that displays the list of restaurant objects, but also has a search form above that a user could enter parameters to filter the results displayed. If no parameters are entered, all the restaurants should be shown. I'm already using haystack and have a search form, but currently it is on a standalone search.html template. I also have an ListView on a separate template, and I guess I'm looking for an end result that combines these.
I did some reading on line and it's unclear what the best way to do it is:
using just listview from Django with some queryset filtering
combining haystack SearchView with django class based views?
this is my best bet so far - creating a customized version of SearchView from Haystack
ideally, ultimately the search capabilities would include things like allow autocompleting the user's inputs, and dynamically filter the results as the user types.
any thoughts on what the best way is to go about this and any examples out there?
There probably is out there some package that gives you everything automatically, displaying the queryset list and allowing simple adding of a search bar and so on (like the admin site). But I take it you are still a beginner to web development, so I'd strongly suggest you drop the listviews, drop haystack, drop everything, and do it all yourself.
See, they're not bad approaches or anything, but they're like shortcuts. And shortcuts are good only if they shorten the original way, which in your case isn't all that long.
To give you an example, here's a simple approach to displaying a list of items:
views.py
def restaraunt_list(request):
restaraunts = Restaraunt.objects.all().order_by('name')[:100] # Just for example purposes. You can order them how you'd like, and you probably want to add pagination instead of limiting the view number arbitrarily
context = {'restaraunts': restaraunts}
return render_to_response('index.html', context)
index.html:
<ul>
{% for rest in restaraunts %}
<li>{{ rest }}</li>
{% endfor %}
</ul>
That's it. Now it displays it in a list. Now to add a search filter all you need to do is this:
index.html (add this anywhere you want it)
<form>
<input type='text' name='q' id='q'></input>
<input type='submit' value='search!'></input>
</form>
When a user sends the data from 'q' it is sent through GET, and then on server side you add this:
def restaraunt_list(request):
restaraunts = Restaraunt.objects.all()
# filter results!
if request.method == 'GET':
q = request.GET['q']
if q != '':
restaraunts = restaraunts.filter(name__contains=q)
# ordering is the last thing you do
restaraunts = restaraunts.order_by('name')[:100]
context = {'restaraunts': restaraunts}
return render_to_response('index.html', context)
Now, this will work, but from what you wrote I understand you want the search to be live the moment a key is pressed. For that, you'd need to use AJAX (go learn how it works, I'm not gonna delve into it here). As for autocomplete, you should check out jQuery UI.
But, like I said, before jumping ahead and using all those stuff, I suggest you first learn the basics. Go through the django tutorial (if you haven't already), and use the amazingly detailed django-docs every step of the way. When some specific things won't work and you're stuck, come here to Stackoverflow and someone will surely help you. good luck!

How to display a post registration welcome message when using both django-registration and django-socialauth?

This is a pretty trivial question but I must be missing something because I can't come up with a solution I'm happy with.
I'm using two libraries to handle registration, django-registration for the email based registration and django-socialauth for the social based registration, and want to display a welcome message when the user registers for the first time.
My current approach is to have a context processor that checks if the user has registered within the past 2 minutes and if so, updates the request object. This seems inefficient since I'm checking every time when it's only used once.
I tried implementing it using signals but the issue I ran into was that I needed some way to hook into the request but only django-registration passes the request along.
An option I'm contemplating is using the signals to update a record in the database but that seems like overkill for something this simple. Am I missing something obvious?
context_processors.py:
def just_registered(request):
just_registered = False
if request.user.is_authenticated() and request.user.email:
if request.user.date_joined < datetime.today() + timedelta(minutes=2):
if 'just_registered' not in request.session:
just_registered = True
request.session['just_registered'] = just_registered
return { 'just_registered' : just_registered }
you can use django messages and implement it in your template
{% if messages %}
{% for message in messages %}
{{message}}
{% endfor %}
{% endif %}
.
def just_registered(request):
if request.user.is_authenticated():
if request.user.date_joined < datetime.today() + timedelta(minutes=2):
messages.info(request, "Welcome")
return ''
user is authenticated is already understood, you don't have to put user email because when you register, the email is required
Just to be clear, you want to display a welcome message when the user successfully logs in for the first time (it says register for the first time in your question)? Do they follow an activation link from an email? You could have that emailed link go to a new user version of your landing page.
Otherwise, if you want to use the same page for normal users and people logging in the for the first time, I don't see how you can avoid checking if this is the user's first time logging in. To do that, it seems like using a boolean attrib on the user (or fk'ed to them) that keeps track of whether they have logged in before would be the way to go, instead of looking at how long ago they activated the account (what if they activated 2 days ago but didn't log in?)
Following from your comment on princesses answer your best bet would be to save some kind of data when the user logs in for the first time.
I would suggest write a simple middleware which detects first login and saves that in a persistent form
Have a look at this:
http://blog.elcodo.pl/post/926902087/django-detect-users-first-login
You can also checkout the middleware in django tracking
https://github.com/codekoala/django-tracking/blob/master/tracking/middleware.py
It is slightly inefficient , however I don't see any other way given the statelessness of HTTP