This is less a technical question, more a "am i doing this in the right way" question.
I have several models defined
class Style(models.Model):
tag_xml = models.TextField()
image = models.ImageField(upload_to="styles")
user = models.ForeignKey(User)
uploaded = models.DateField()
class StyleMatch(models.Model):
style = models.ForeignKey(Style)
item = models.ForeignKey(FashionItem)
they can't be populated via html forms simply due to the nature of the task, so in order to populate them I have a html page with jquery and lots of event functions and other javascript goodies. When the save button is clicked I call .ajax() and pass all of the collected variables
var saveRequest= $.ajax({
url: "/save_style/",
type: "POST",
data: "selection="+s+"&user="+user+"&src="+image_src,
dataType: "text"
});
My save_style view then saves the values into the model
def save_style(request):
if request.method == 'POST':
selection = request.POST['selection'].rsplit("|")
user = request.POST['user']
src = request.POST['src']
f = open(MEDIA_ROOT+src)
image_file = File(f)
u = User.objects.get(id=user)
style = Style(tag_xml = "",
image = image_file,
user = u,
uploaded = date.today())
style.save()
for s in selection:
if (s != ''):
match = FashionItem.objects.get(id=s)
styleMatch = StyleMatch(style = style,
item = match)
styleMatch.save()
i = StyleMatch.objects.filter(style=style)
items = FashionItem.objects.filter(id__in=i)
return render_to_response('style_saved.html', dict(image=src, items=items, media_url = MEDIA_URL), context_instance=RequestContext(request))
After doing this I really want to go to a success page and display the records I have just added to the model, however if I use render_to_response and pass back the model details I have to rebuild the entire page in javascript, it seems better to redirect to a new template, but if I use HttpResponseRedirect a) I can't pass back values and b) it doesn't appear to be redirecting quite right (I think because the post is originating from my javascript).
So finally my questions
Is this really how I should be doing this? The django doc doesn't
really seem to cover these slightly more complicated areas, so I'm a
little unsure.
Should I be using render_to_response or
HttpResponseRedirect above? Or possibly a third option I don't know
about.
Any suggestions appreciated.
FYI I know the code above is not ideal i.e. missing validation, comments ... etc, its simply been provided for demonstration purposes. Feel free to point out any serious issues though.
Depending on the nature of your application, you probably shouldn't be building the entirety of your pages with JavaScript. However, since we're there already I've used the following solution with nice results:
Consider creating a template "fragment", as I call them. It's simply a bit of HTML that is designed to be a capsule for data transferred via AJAX. Do a render_to_response to this fragment, pass in your processed view data as variables, then retrieve this data via AJAX and use JavaScript to replace the HTML within a designated div element with the returned data.
There are some pitfalls with the above solution, such as styling and event handler attachment on the template fragment, but it should at least get you working. Just a tip in this regard, become familiar with jQuery's .on().
pass all of the collected variables
Why don't $(form).serialize()?
saves the values into the model
Why don't use django.forms.ModelForm (or few of them)?
doesn't appear to be redirecting quite right
Because redirects in AJAX are processed in AJAX call and do not affect opened page unless you process received data in JS somehow.
Also, you don't have any data validation and|or error reporting, that's bad. Actually, ModelForm should provide a huge help with that.
Related
I have couple of functions which captures ajax requests. I get the data from the request and put into the array. Then I loopthru this array in another function for instance to pass the data to context. This solution basically works, but is it a good way of doing things ? Appreciate for any feedback.
piece of code:
user_choices = []
#login_required
#csrf_exempt
def make_order(request):
if request.method == "POST" and request.is_ajax():
data = json.loads(request.body)
for order in data["array"]:
user_choices.append(order)
return HttpResponse(200)
else:
return redirect(request, 'home')
movie = 0
seats = []
#login_required
def confirmation(request):
if len(user_choices) > 0:
movie = Movies.objects.get(pk=int(user_choices[0]["id"]))
for seat in user_choices:
seats.append(seat["row"]+":"+seat["seat"])
context = {
"movie":movie.title,
"seats":seats
}
return render(request, "main_templates/confirmation.html", context)
else:
return redirect("home")
I have couple of functions which captures ajax requests. I get the data from the request and put into the array. Then I loopthru this array in another function for instance to pass the data to context. This solution basically works, but is it a good way of doing things?
No, you introduce an anti-pattern called global state [se]. Global state is a serious anti-pattern because it makes a program unpredictable. Depending on previous requests, the list can already contain data. Normally a GET request should have no side-effects. By altering lists, that is no longer the case.
Furthermore in this specific case, it means that if one user queries for data, and then another user makes a request for data, the data of the first user "leaks" to the second user.
But nevertheless, even if you manage to make it more safe, a global state introduces a lot of difficulties. These are discussed in the software engineering post. While you can of course each time aim to manually fix these issues, it will result in a lot of work and bugs, and therefore more trouble than it is worth.
I want to test redirection of Update View.
Reading the documentations, and looking at source code, I find:
Update View instances self.object before processing the request (which helps it to set form fields to current value)
Now this is behavior I wanted, but is giving me trouble during testing.
My Models.py
class Project(models.Model):
title = models.CharField(max_length=50)
description = models.CharField(max_length=200, blank=True, default="")
# Other fields
views.py
class UpdateProject(LogInRequiredMixin, UpdateView):
form_class = ProjectUpdateForm
template_name = 'project/create.html'
And In Testing:
(I have created a project and set up most things in setUp)
def test_redirection(self):
# After updating project, users should land on correct page.[View Project page].
self.client.login(username=self.owner.email, password=self.password)
response = self.client.post(self.url, follow=True) # This will give error, since it requires Title field
self.assertRedirects(response, reverse('project:show',
kwargs={'project_id': self.project.id}))
Now, I know I can pass title explicitly with data field, but if I were to change the fields later in model, this test would also fail, which it shouldn't [It's only purpose is to check for redirection, there are other tests which deal with form validation, etc]
So, my question is: Is there a way to simulate Post Request as done by Update View (i.e. pass data context by setting object context to pre-filled values and only over-ride changed values]
The view presumably only redirects on success. If you don't pass enough information for the view to succeed, then it shouldn't redirect. So it sounds like you want something impossible — you're wanting to test what the code does in a situation other than the situation you are setting up.
The question I have is: what is the value of this test? Why not just put it at the end of the test that checks for success?
I understand that the normal testing mantra is that you have a test for one thing only, but I think it's just impractical for this kind of scenario, and the cost is fragile tests that hack around the internals of something.
The other approach is that you split you write your tests like this:
def _submit_good_form_data(self):
response = self.client.post(self.url, follow=True, data={'title':'The title')
def test_form_success(self):
response = self._submit_good_data()
# asserts here
def test_redirection(self):
response = self._submit_good_data()
# asserts here
This way you only have one place to update when the nature of your form changes.
However, I'd question whether this is really worth it, seeing you are adding significantly to the length of the test run, and it doesn't actually give you the test independence you are really after (an error in the view will cause both tests to fail).
One way to replicate behind-the-scenes machinery:
get_response = self.client.get(self.url)
response = self.client.post(self.url, data=get_response.context['object'].__dict__, follow=True)
self.assertRedirects(response, reverse('project:show', kwargs={'project_id': self.project.id}))
Not very pretty, but is basically what goes behind the scenes.
So I found this answer regarding setting session variables in a pyramid view file, and then later accessing it in a mako template. ( How to access session variable in Mako template and Pyramid? )
I wanted to know if you could do it the other way around. So instead of:
Pyramid view.py
def thisView(request):
session = request.session
session['selectedclientid'] = 'test' #selectedclient.id
session.save()
webpage.mako
${request.session['selectedclientid']}
Can I swap it so I can do this instead?
webpage.mako
${request.session['selectedclientid'] = '5'}
Pyramid view.py
def thisView(request):
someLogicOn(session['selectedclientid'])
So far I have been unsuccessful in making it work and I'm not sure if it's just due to a lack of understanding how to do it or if it's something that just can't be done. Any advice would be great!
In the typical rendering workflow, the view executes before the renderer. It's not clear how you are intending to correct for that. It's possible to do if you call render yourself within the view, I guess, so I'll show that.
webpage.mako:
<%
request.session['selectedClientId'] = '5'
%>
code:
def thisView(request):
response = render_to_response('webpage.mako', {}, request=request)
someLogicOn(request.session['selectedClientId'])
return response
This is logically a little backward though, so you might want to think twice about what you're doing.
I have two types of users. After a user logs in, I take the user to their user profile at /profile
Based on the type of user, their profile may contain different navigation items and forms. Is there an easier way to construct the navigation based on the user type instead of putting {% if %} tags everywhere in the template.
Not sure why #dm03514 deleted his answer, but I'll post something like it, since it would've been my thoughts as well.
If the templates are different enough, then you're right, branching all over the place is a bad idea. It'll just make your template a confusing mess. So just create a separate template for each user type. Then, in your view, you can choose one template or another to show based on the user type. For example:
Old-style function-based views:
def profile_view(request):
if request.user.get_profile().type == 'foo':
template = 'path/to/foo_profile.html'
elif request.user.get_profile().type == 'bar':
template = 'path/to/bar_profile.html'
else:
template = 'path/to/generic_profile.html' # if you want a default
return render_to_response(template, {'data': 'data'}, context_instance=RequestContext(request))
New-style class-based views:
class MyView(View):
def get_template_names(self):
if self.request.user.get_profile().type == 'foo':
return ['path/to/foo_profile.html']
elif self.request.user.get_profile().type == 'bar':
return ['path/to/bar_profile.html']
else:
return ['path/to/generic_profile.html']
I'm not sure how your user types are set up, but if it's based on a string value, you can even create something much more automated, like:
template = 'path/to/%s_profile.html' % request.user.get_profile().type
Then, just create a template for each type based on that naming scheme.
And, of course, each template can extend a base profile.html template, allowing you factor out some common functionality.
Create a profile model and link the user to this profile model. (via a foreign key)
The profile model will then have different attributes for different behaviours.
These different behaviours would then be exposed in the web application either by branching the template (as you say using if in the template) or returning different data to the template or being sent to javascript functions in the font end to modify behaviour
Another alternative is to direct users to different pages based of there profile. The challenge with this is different urls will be needed to different users. This may lead to lots of code duplication.
Have you thought about using permissions for that?
I do same with custom permissions. Easy part is that auth provides anything you need, and it's always there in request.
Very similar code to what Chris Patt proposed would be used:
def profile_view(request):
if request.user.has_perm('permission_codename_foo'):
template = 'path/to/foo_profile.html'
elif request.user.has_perm('permission_codename_bar'):
template = 'path/to/bar_profile.html'
else:
template = 'path/to/generic_profile.html' # if you want a default
return render_to_response(template, {'data': 'data'}, context_instance=RequestContext(request))
what's useful, is that you can protect your views using same permissions with decorators:
#permission_required('permission_codename_foo')
def foo():
#your view here
or if you want to check for permission alternatives:
#user_passes_test(lambda u: u.has_perm('permission_codename_foo') or u.has_perm('permission_codename_bar'))
def foo_or_bar():
#your view here
django creates whole bunch of default permissions as well named: app_name.add_modelname, app_name.change_modelname, and app_name.delete_modelname if you need to limit user's possibilities
If you need to change parts of templates, try {% include %} to load those parts from different files.
I would like to be able to submit a form in an HTML source (string). In other words I need at least the ability to generate POST parameters from a string containing HTML source of the form. This is needed in unit tests for a Django project. I would like a solution that possibly;
Uses only standard Python library and Django.
Allows parameter generation from a specific form if there is more than one form present.
Allows me to change the values before submission.
A solution that returns a (Django) form instance from a given form class is best. Because it would allow me to use validation. Ideally it would consume the source (which is a string), a form class, and optionally a form name and return the instance as it was before rendering.
NOTE: I am aware this is not an easy task, and probably the gains would hardly justify the effort needed. But I am just curious about how this can be done, in a practical and reliable way. If possible.
You should re-read the documentation about Django's testing framework, specifically the part about testing views (and forms) with the test client.
The test client acts as a simple web browser, and lets you make GET and POST requests to your Django views. You can read the response HTML or get the same Context object the template received. Your Context object should contain the actual forms.Form instance you're looking for.
As an example, if your view at the URL /form/ passes the context {'myform': forms.Form()} to the template, you could get to it this way:
from django.test.client import Client
c = Client()
# request the web page:
response = c.get('/form/')
# get the Form object:
form = response.context['myform']
form_data = form.cleaned_data
my_form_data = {} # put your filled-out data in here...
form_data.update(my_form_data)
# submit the form back to the web page:
new_form = forms.Form(form_data)
if new_form.is_valid():
c.post('/form/', new_form.cleaned_data)
Hopefully that accomplishes what you want, without having to mess with parsing HTML.
Edit: After I re-read the Django docs about Forms, it turns out that forms are immutable. That's okay, though, just create a new Form instance and submit that; I've changed my code example to match this.
Since the Django test framework does this, I'm not sure what you're asking.
Do you want to test a Django app that has a form?
In which case, you need to do an initial GET
followed by the resulting POST
Do you want to write (and test) a Django app that submits a form to another site?
Here's how we test Django apps with forms.
class Test_HTML_Change_User( django.test.TestCase ):
fixtures = [ 'auth.json', 'someApp.json' ]
def test_chg_user_1( self ):
self.client.login( username='this', password='this' )
response= self.client.get( "/support/html/user/2/change/" )
self.assertEquals( 200, response.status_code )
self.assertTemplateUsed( response, "someApp/user.html")
def test_chg_user( self ):
self.client.login( username='this', password='this' )
# The truly fussy would redo the test_chg_user_1 test here
response= self.client.post(
"/support/html/user/2/change/",
{'web_services': 'P',
'username':'olduser',
'first_name':'asdf',
'last_name':'asdf',
'email':'asdf#asdf.com',
'password1':'passw0rd',
'password2':'passw0rd',} )
self.assertRedirects(response, "/support/html/user/2/" )
response= self.client.get( "/support/html/user/2/" )
self.assertContains( response, "<h2>Users: Details for", status_code=200 )
self.assertContains( response, "olduser" )
self.assertTemplateUsed( response, "someApp/user_detail.html")
Note - we don't parse the HTML in detail. If it has the right template and has the right response string, it has to be right.
It is simple... and hard at the same time.
Disclaimer: I don't know much about Python and nothing at all about Django... So I give general, language agnostic advices...
If one of the above advices doesn't work for you, you might want to do it manually:
Load the page with an HTML parser, list the forms.
If the method attribute is POST (case insensitive), get the action attribute to get the URL of the request (can be relative).
In the form, get all input and select tags. The name (or id if no name) attributes are the keys of the request parameters. The value attributes (empty if absent) are the corresponding values.
For select, the value is the one of the selected option or the displayed text is no value attribute.
These names and values must be URL encoded in GET requests, but not in POST ones.
HTH.
Check out mechanize or it's wrapper twill. I think it's ClientForm module will work for you.