I'm still quite new to python with less than a year of experience, and I've been learning it through building a rather large project on google app engine. It's grown to be a behemoth of 10k+ lines of code and html templates, so I'm in the process of refactoring a rather large portion of the code to follow a much more rigorous MVC architecture.
My question is one concerning python directly. I don't know the words to say exactly what I want to build, so I would just like to use an example.
This is my current "basic" code for displaying a view:
class MainHandler(webapp.RequestHandler):
def get(self):
tUser = users.get_current_user()
tContext = {
'user': tUser,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
#User is logged in
if (tUser):
#code for loading view information and adding to the context dict
#CUSTOMIZATION GOES HERE
else:
self.redirect("/")
tmpl = os.path.join(os.path.dirname(__file__), 'logged-in.html')
self.response.out.write(render(tmpl, tContext))
I would like to take this boilerplate code and abstract it somehow, maybe with a way of prepending/appending the "customizable" code for each class method?
I think I might be able to use a decorator to do this somehow, but I have no python mentors outside of stackoverflow to point me in the right direction. I would prefer the most pythonic method possible, or at least what's generally considered "best practices" in this situation.
The python version is 2.7.2.
edit
Note, if I can do this with decorators, then what is necessary for me to be able to call the decorator from an entirely different class and python file? I would like to be able put my decorators in one file and reference it from elsewhere so my code is as normalized as is reasonable. =)
edit 2
This is the testing code that I worked out in the console, and I have to leave for the evening or I would refine it more. However, it appears that this successfully accesses and modifies the class's properties, which is pretty much what I think you need to pull this off in GAE.
class Decorators():
#staticmethod
def BeginInit(pInFunction):
def OutFunction1(self):
print str(self.innerv)
pInFunction(self)
return OutFunction1
#staticmethod
def EndInit(pInFunction):
def OutFunction2(self):
self.innerv = 5
pInFunction(self)
print "this is test 3"
return OutFunction2
class Test2Handler():
innerv = 10
#Decorators.BeginInit
#Decorators.EndInit
def TestPrint(self):
print self.innerv
print "this is test 2"
Prints
10
5
this is test 2
this is test 3
Instead of using decorators, you could use a base class for your request handlers, like so
class HandlerBase(webapp.RequestHandler):
def get_context(self):
return {}
def get(self):
tUser = users.get_current_user()
tContext = {
'user': tUser,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
# tContext holds the defaults for your context
#User is logged in
if (tUser):
# if the dict returned by self.get_context() contains a key
# that's already in tContext, tContext[key] will be overridden
# with self.get_context()[key]
tContext.update(self.get_context())
else:
self.redirect("/")
tmpl = os.path.join(os.path.dirname(__file__), 'logged-in.html')
self.response.out.write(render(tmpl, tContext))
class MainHandler(HandlerBase):
def get_context(self):
# the contents of the following dict will be added to
# the context in HandlerBase
return {'greeting': 'Hi!'}
Related
I am relatively new to Django and very new to writing unit tests. I'd like to ask for assistance but I'm a bit stuck with where to even begin. The app I'm working with allows a teacher to assign multiple assignments to a student. On the student dashboard, an assignment should only be available if the start date <= today's date. The student should only see the first assignment in the list.
I need to compose a unit test to cover this scenario:
manually assign multiple assignments to a student
use the same query that is used for the student dashboard to check that the only assignments returned are the ones with a
start date <= today's date
check that the student only sees the first assignment (with the earliest start date) in the list.
Below I have posted the relevant code that is pulling what displays on the student dashboard. Please let me know if additional code is needed to help me get started with this. Thanks very much for any help you can offer!
Edit: I would like to only use the built in django.test features for now, if possible
from my home/views.py file
#login_required
def index(request):
user_type = request.user.type.text
if user_type == 'Student':
""" Only return the first test so the student sees one test at a time"""
assignment = Assignment.objects.filter(
student=request.user,
start_date__lte=datetime.date.today(),
completed=False).first()
if (assignment):
context = {
'test_pk': assignment.test.pk,
}
else:
context = {}
return render(request, 'home/student.html', context)
Basics of testing stuff like this goes roughly like:
Create the desired data manually
Create the action/conditions that are happening in the view(maybe send a request to the view)
Check the result with the previously manually created data.
So, start with creating some Assignment objects for students.
Run your view(send a request to your view logged in as the previously created user)
Check if the desired outcome exist in the returned html.
I would suggest you to use pytest and factoryboy for that, there's a lot of great tutorials online to use it with Django.
For you example it would be something like this
You need first to init the session, we can create fixture for that
import pytest
import factory
#pytest.fixture
def client():
from django.test.client import Client
return Client(HTTP_USER_AGENT='pytest')
then we should init the session, another fixture:
#pytest.fixture
def session(client):
# your custom session her
user = #use factory for the user
client.user = user
# your defaults for
# client.GET
# client.POST
# client.META
return client
class AssignmentFactory(factory.django.DjangoModelFactory):
class Meta:
model = Assignment
django_get_or_create = ('any attribute you like',)
# default the attributes you want to create here
# For example
name = "assignment one"
Then the test can be something like this
#pytest.mark.django_db
def test_retrieve_assignment_success(session):
path = reverse("view_name")
assignment = AssignmentFactory()
res = session.get(path=path, data={}, follow=False)
json_res = res.json()
assert json_res.get('context') is not None
assert assigment.pk == json_res.get('context').get('test_pk')
#pytest.mark.django_db
def test_retrieve_assignment_fail(session):
path = reverse("view_name")
res = session.get(path=path, data={}, follow=False)
json_res = res.json()
assert json_res.get('context') is not None
assert json_res.get('context') == {}
I have a function that gets some base information in my views.py file, and I'm trying to update the context of each page using it by having it return a dictionary. However, using .update() on the context dictionary in the render() function doesn't seem to work.
Here's what I'm doing:
def getBaseInfo():
allPages = list(Page.objects.all())
primaryPages = allPages[:5]
secondaryPages = allPages[5:]
return {'p':primaryPages, 'p2':secondaryPages}
def index(request):
return render(request, 'pages/index.html', {}.update(getBaseInfo()))
However, nothing is sent to my templates. Thanks in advance!
Edit: I'm using Python 2.7.11
Firstly, if you wanted to use a base dictionary and add objects to that you should do so explicitly:
def index(request):
context = getBaseInfo()
context.update({'otherkey': 'othervalue'})
# or
context['otherkey'] = 'othervalue'
return(...)
However, there is no need to do this at all. Django already provides you a way of automatically providing shared context, and that is a context processor.
In fact your getBaseInfo() function is already almost a context processor - it just needs to accept the request parameter - so you just need to add it to the context_processors list in your TEMPLATES setting. Then all your templates will automatically get the values from that function.
You should do something like this:
def index(request):
allPages = list(Page.objects.all())
primaryPages = allPages[:5]
secondaryPages = allPages[5:]
return render(request, 'pages/index.html', {'p':primaryPages, 'p2':secondaryPages})
Other option should be to make getBaseInfo a #property for reusability and DRY purposes, or make the view class based template view and define reusable code as mixin. I prefer the latter, but it's entirely matter of personal choice.
I build web application with Django REST Framework. There is one simple view, which return reference Information with db fields.
resources.py:
RESOURCES = {
'genres': GenreSerializer(Genre.objects.all(), many=True).data,
'authors': AuthorSerializer(Author.objects.all(), many=True).data,
......
}
class ResourceApiView(views.APIView):
def get(self, request):
params = request.query_params
response_dict = {}
if params:
# Return RESOURSES based on query params
for i in params:
q = RESOURCES.get(i)
if q:
response_dict[i] = q
else:
# Return all RESOURSES
response_dict = RESOURCES
return Response(response_dict,
status=status.HTTP_200_OK
)
It works fine, but when I add new object to one the resources querysets. Nothing happens, it show old queries.
I tried printed RESOURSES in my module, it printed once and other get requests don't trigger it.
Then I move RESOURSES directly in class ResourceApiView and it's behavior same like when RESOURSES where in module.
class ResourceApiView(views.APIView):
RESOURCES = {
'genres': GenreSerializer(Genre.objects.all(), many=True).data,
'authors': AuthorSerializer(Author.objects.all(), many=True).data,
......
}
def get(self, request):
...
It work fine only when I put RESOURSES in get method.
class ResourceApiView(views.APIView):
def get(self, request):
RESOURCES = {
'genres': GenreSerializer(Genre.objects.all(), many=True).data,
'authors': AuthorSerializer(Author.objects.all(), many=True).data,
......
}
But why is it happening? Why I can't evaluate queries from class attributes for each method call?
this is more related to python than to django. Let's say you hava file lib.py
def say_hello():
print "hello"
GREETINGS = {
"hello": say_hello()
}
now go to another python file (or the shell) and just import your lib.py, you'll print "hello" to the console because when you import the file It starts resolving the code inside so it's creating the GREETINGS variable (RESOURCES in your case) and calling the say_hello() method, for you it's executing the query. However python is smart enough that if you import the file again he'll remember that you just imported it before so it wont load the module again, cause it already has the module reference saved.
Your query is beeing executed once when the view was first loaded, and reimporting the view wont make the reference change
The same for placing RESOURCES as a class attribute. The code was executed when the class was imported (again you can test it by creating a class on the lib.py example)
hope this clarifies :) but maybe the docs explains it better https://docs.python.org/2/tutorial/modules.html
Note:
I think that the .data on the serializer is actually executing the query. Without it your query and the serializer would just be stored as reference, because the ORM is lazy. Change your RESOURCES to improve the performance of your endpoint because right now if you request one single resource (e.g. 'authors) its still executing ALL the queries ('authors', 'genres', etc)
I know that webapp2_cached_property replaces a method with data after the first call for each call to it thereafter, and thus my problems arise.
I have a multilingual site and I am using a form to build some simple select menus. The select menus are data that change language. Obviously if a user changes the language on the system I would like to rebuild the form. However, I don't want to remove the webapp2_cached_property as it will then rebuild the form every time the user calls the same url and that will slow down the system. So does anyone know a way to force webapp2_cached_property to re-evaluate on demand, e.g. when client changes language. At the moment I have everything else in the selected language but the select data in the default language. Anything down and dirty will do! Ah, yes this only happens on production and not on the dev server...
class HomeRequestHandler(BaseHandler):
"""
Handler to show the home page
"""
def get(self):
params = {}
params['products'] = []
params['max_searches'] = 1
params['user_search_count'] = 0
return self.render_template('index.html', **params)
#webapp2.cached_property
def form(self):
import product.product_data.forms as forms
return forms.ProductForm(self)
Ok, I have tried the following but still the language is not changing in production...
Added this to my base handler - it's working!
if hasattr(self, 'form'):
if self.locale != self.oldLocale and hasattr(self, 'form_no_cache'):
new_form = self.form_no_cache
kwargs['form'] = new_form()
logging.info('reset form')
logging.info(kwargs['form'].product_type())
else:
kwargs['form'] = self.form
logging.info('using cached form')
And added this to my home handler
def form_no_cache(self):
import product.product_data.forms as forms
return forms.ProductForm(self)
All this is fine in development and the logs seem correct in development and production...
Any ideas people?
ok, couldn't figure this one out, so rewrote the code to build selects in a simple def and pass as a parameter instead of form. Seems to be working. I have my suspicions regarding wtforms though, but it's a pain not knowing, but no time...
I'm trying to update an endpoint which I have defined previously as follows:
class NestedMessage(messages.Message):
foo = messages.StringField(1)
MyResource = endpoints.ResourceContainer(
message_types.VoidMessage,
param1=messages.StringField(1, required=True),
param2=messages.StringField(2),
param3=messages.MessageField(NestedMessage, 3)
)
class MyApi(remote.Service):
#endpoints.method(MyResource, messages.VoidMessage,
path='new', http_method='POST', name='new')
def new(self, request):
# ...
return messages.VoidMessage
I need to change from using a ResourceContainer to a Message class instead (it was unclear which one to use for POST methods).
So I try and update to this:
# NestedMessage unchanged
class NestedMessage(messages.Message):
foo = messages.StringField(1)
class MyMessage(messages.Message):
param1 = messages.StringField(1, required=True)
param2 = messages.StringField(2)
param3 = messages.MessageField(NestedMessage, 3)
class MyApi(remote.Service):
#endpoints.method(MyMessage, messages.VoidMessage,
path='new', http_method='POST', name='new')
def new(self, request):
# ...
return messages.VoidMessage
But when I try the endpoint in /_ah/api/explorer, it just shows a blank page. Checking the result of discovery.apis.getRest on my api, the new MyMessage class isn't listed, and the MyApi.new method is borked due to not having a resource container.
Renaming the method to something else (e.g. MyApi.create) works, but then it breaks the MyApi.new method.
Is there a way to "force" Cloud Endpoints to update all methods?
EDIT This is both on development (localhost) and when deploying.
EDIT 2 It seems the issue only happens with API Explorer. Using the updated endpoint (via the Javascript library for example) uses the working, updated method.
EDIT 3 No wait, I've updated two endpoint methods. Now, both of them are showing fine in API Explorer, but one of them still seems to be "broken" when used with the Javascript client library (i.e. POST payload is passed as URL query parameters, not as JSON body). The other is behaving correctly. What is going on?