|
1 #!/usr/bin/python2.5 |
|
2 # |
|
3 # Copyright 2008 the Melange authors. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 |
|
17 """Views for editing and examining Documents. |
|
18 """ |
|
19 |
|
20 __authors__ = [ |
|
21 '"Todd Larsen" <tlarsen@google.com>', |
|
22 ] |
|
23 |
|
24 |
|
25 from google.appengine.api import users |
|
26 |
|
27 from django import forms |
|
28 from django import http |
|
29 from django.utils.translation import ugettext_lazy |
|
30 |
|
31 from soc.logic import models |
|
32 from soc.logic import out_of_band |
|
33 from soc.logic import path_link_name |
|
34 from soc.logic.models import document |
|
35 |
|
36 from soc.views import helper |
|
37 from soc.views import simple |
|
38 from soc.views.helper import access |
|
39 from soc.views.helper import decorators |
|
40 from soc.views.user import profile |
|
41 |
|
42 import soc.models.document |
|
43 import soc.views.helper.forms |
|
44 import soc.views.helper.requests |
|
45 import soc.views.helper.responses |
|
46 import soc.views.helper.widgets |
|
47 import soc.views.out_of_band |
|
48 |
|
49 |
|
50 DEF_CREATE_NEW_DOC_MSG = ' You can create a new document by visiting the' \ |
|
51 ' <a href="/docs/edit">Create ' \ |
|
52 'a New Document</a> page.' |
|
53 |
|
54 SUBMIT_MESSAGES = ( |
|
55 ugettext_lazy('Document saved.'), |
|
56 ) |
|
57 |
|
58 |
|
59 def getDocForForm(form): |
|
60 """Extracts doc fields from a form and creates a new doc from it |
|
61 """ |
|
62 |
|
63 user = users.get_current_user() |
|
64 if user: |
|
65 email = user.email() |
|
66 else: |
|
67 email = None |
|
68 |
|
69 partial_path = form.cleaned_data.get('partial_path') |
|
70 link_name = form.cleaned_data.get('link_name') |
|
71 |
|
72 properties = {} |
|
73 properties['partial_path'] = partial_path |
|
74 properties['link_name'] = link_name |
|
75 properties['title'] = form.cleaned_data.get('title') |
|
76 properties['short_name'] = form.cleaned_data.get('short_name') |
|
77 properties['content'] = form.cleaned_data.get('content') |
|
78 properties['author'] = models.user.logic.getFromFields(email=email) |
|
79 properties['is_featured'] = form.cleaned_data.get('is_featured') |
|
80 |
|
81 doc = document.logic.updateOrCreateFromFields(properties, |
|
82 partial_path=partial_path, |
|
83 link_name=link_name) |
|
84 return doc |
|
85 |
|
86 |
|
87 class CreateForm(helper.forms.DbModelForm): |
|
88 """Django form displayed when Developer creates a Document. |
|
89 """ |
|
90 content = forms.fields.CharField(widget=helper.widgets.TinyMCE( |
|
91 attrs={'rows':10, 'cols':40})) |
|
92 |
|
93 class Meta: |
|
94 model = soc.models.document.Document |
|
95 |
|
96 #: list of model fields which will *not* be gathered by the form |
|
97 exclude = ['inheritance_line', 'author', 'created', 'modified'] |
|
98 |
|
99 def clean_partial_path(self): |
|
100 partial_path = self.cleaned_data.get('partial_path') |
|
101 # TODO(tlarsen): combine path and link_name and check for uniqueness |
|
102 return partial_path |
|
103 |
|
104 def clean_link_name(self): |
|
105 link_name = self.cleaned_data.get('link_name') |
|
106 # TODO(tlarsen): combine path and link_name and check for uniqueness |
|
107 return link_name |
|
108 |
|
109 |
|
110 DEF_DOCS_CREATE_TMPL = 'soc/docs/edit.html' |
|
111 |
|
112 @decorators.view |
|
113 def create(request, page=None, template=DEF_DOCS_CREATE_TMPL): |
|
114 """View to create a new Document entity. |
|
115 |
|
116 Args: |
|
117 request: the standard django request object |
|
118 page: a soc.logic.site.page.Page object which is abstraction that combines |
|
119 a Django view with sidebar menu info |
|
120 template: the "sibling" template (or a search list of such templates) |
|
121 from which to construct the public.html template name (or names) |
|
122 |
|
123 Returns: |
|
124 A subclass of django.http.HttpResponse which either contains the form to |
|
125 be filled out, or a redirect to the correct view in the interface. |
|
126 """ |
|
127 |
|
128 try: |
|
129 access.checkIsDeveloper(request) |
|
130 except soc.views.out_of_band.AccessViolationResponse, alt_response: |
|
131 # TODO(tlarsen): change this to just limit the Documents that can be |
|
132 # created by the User in their current Role |
|
133 return alt_response.response() |
|
134 |
|
135 # create default template context for use with any templates |
|
136 context = helper.responses.getUniversalContext(request) |
|
137 context['page'] = page |
|
138 |
|
139 if request.method == 'POST': |
|
140 form = CreateForm(request.POST) |
|
141 |
|
142 if form.is_valid(): |
|
143 doc = getDocForForm(form) |
|
144 |
|
145 if not doc: |
|
146 return http.HttpResponseRedirect('/') |
|
147 |
|
148 new_path = path_link_name.combinePath([doc.partial_path, doc.link_name]) |
|
149 |
|
150 # redirect to new /docs/edit/new_path?s=0 |
|
151 # (causes 'Profile saved' message to be displayed) |
|
152 return helper.responses.redirectToChangedSuffix( |
|
153 request, None, new_path, |
|
154 params=profile.SUBMIT_PROFILE_SAVED_PARAMS) |
|
155 else: # method == 'GET': |
|
156 # no link name specified, so start with an empty form |
|
157 form = CreateForm() |
|
158 |
|
159 context['form'] = form |
|
160 |
|
161 return helper.responses.respond(request, template, context) |
|
162 |
|
163 |
|
164 DEF_DOCS_EDIT_TMPL = 'soc/docs/edit.html' |
|
165 |
|
166 class EditForm(CreateForm): |
|
167 """Django form displayed a Document is edited. |
|
168 """ |
|
169 doc_key_name = forms.fields.CharField(widget=forms.HiddenInput) |
|
170 created_by = forms.fields.CharField(widget=helper.widgets.ReadOnlyInput(), |
|
171 required=False) |
|
172 |
|
173 |
|
174 @decorators.view |
|
175 def edit(request, page=None, partial_path=None, link_name=None, |
|
176 template=DEF_DOCS_EDIT_TMPL): |
|
177 """View to modify the properties of a Document Model entity. |
|
178 |
|
179 Args: |
|
180 request: the standard django request object |
|
181 page: a soc.logic.site.page.Page object which is abstraction that combines |
|
182 a Django view with sidebar menu info |
|
183 partial_path: the Document's site-unique "path" extracted from the URL, |
|
184 minus the trailing link_name |
|
185 link_name: the last portion of the Document's site-unique "path" |
|
186 extracted from the URL |
|
187 template: the "sibling" template (or a search list of such templates) |
|
188 from which to construct the public.html template name (or names) |
|
189 |
|
190 Returns: |
|
191 A subclass of django.http.HttpResponse which either contains the form to |
|
192 be filled out, or a redirect to the correct view in the interface. |
|
193 """ |
|
194 |
|
195 try: |
|
196 access.checkIsDeveloper(request) |
|
197 except soc.views.out_of_band.AccessViolationResponse, alt_response: |
|
198 # TODO(tlarsen): change this to just limit the Documents that can be |
|
199 # edited by the User in their current Role |
|
200 return alt_response.response() |
|
201 |
|
202 # create default template context for use with any templates |
|
203 context = helper.responses.getUniversalContext(request) |
|
204 context['page'] = page |
|
205 |
|
206 doc = None # assume that no Document entity will be found |
|
207 |
|
208 path = path_link_name.combinePath([partial_path, link_name]) |
|
209 |
|
210 # try to fetch Document entity corresponding to path if one exists |
|
211 try: |
|
212 if path: |
|
213 doc = document.logic.getFromFields(partial_path=partial_path, |
|
214 link_name=link_name) |
|
215 except out_of_band.ErrorResponse, error: |
|
216 # show custom 404 page when path doesn't exist in Datastore |
|
217 error.message = error.message + DEF_CREATE_NEW_DOC_MSG |
|
218 return simple.errorResponse(request, page, error, template, context) |
|
219 |
|
220 if request.method == 'POST': |
|
221 form = EditForm(request.POST) |
|
222 |
|
223 if form.is_valid(): |
|
224 doc = getDocForForm(form) |
|
225 |
|
226 if not doc: |
|
227 return http.HttpResponseRedirect('/') |
|
228 |
|
229 new_path = path_link_name.combinePath([doc.partial_path, doc.link_name]) |
|
230 |
|
231 # redirect to new /docs/edit/new_path?s=0 |
|
232 # (causes 'Profile saved' message to be displayed) |
|
233 return helper.responses.redirectToChangedSuffix( |
|
234 request, path, new_path, |
|
235 params=profile.SUBMIT_PROFILE_SAVED_PARAMS) |
|
236 else: # method == 'GET': |
|
237 # try to fetch Document entity corresponding to path if one exists |
|
238 if path: |
|
239 if doc: |
|
240 # is 'Profile saved' parameter present, but referrer was not ourself? |
|
241 # (e.g. someone bookmarked the GET that followed the POST submit) |
|
242 if (request.GET.get(profile.SUBMIT_MSG_PARAM_NAME) |
|
243 and (not helper.requests.isReferrerSelf(request, suffix=path))): |
|
244 # redirect to aggressively remove 'Profile saved' query parameter |
|
245 return http.HttpResponseRedirect(request.path) |
|
246 |
|
247 # referrer was us, so select which submit message to display |
|
248 # (may display no message if ?s=0 parameter is not present) |
|
249 context['notice'] = ( |
|
250 helper.requests.getSingleIndexedParamValue( |
|
251 request, profile.SUBMIT_MSG_PARAM_NAME, |
|
252 values=SUBMIT_MESSAGES)) |
|
253 |
|
254 # populate form with the existing Document entity |
|
255 author_link_name = doc.author.link_name |
|
256 form = EditForm(initial={'doc_key_name': doc.key().name(), |
|
257 'title': doc.title, 'partial_path': doc.partial_path, |
|
258 'link_name': doc.link_name, 'short_name': doc.short_name, |
|
259 'content': doc.content, 'author': doc.author, |
|
260 'is_featured': doc.is_featured, 'created_by': author_link_name}) |
|
261 else: |
|
262 if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME): |
|
263 # redirect to aggressively remove 'Profile saved' query parameter |
|
264 return http.HttpResponseRedirect(request.path) |
|
265 |
|
266 context['lookup_error'] = ugettext_lazy( |
|
267 'Document with that path not found.') |
|
268 form = EditForm(initial={'link_name': link_name}) |
|
269 else: # no link name specified in the URL |
|
270 if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME): |
|
271 # redirect to aggressively remove 'Profile saved' query parameter |
|
272 return http.HttpResponseRedirect(request.path) |
|
273 |
|
274 # no link name specified, so start with an empty form |
|
275 form = EditForm() |
|
276 |
|
277 context.update({'form': form, |
|
278 'existing_doc': doc}) |
|
279 |
|
280 return helper.responses.respond(request, template, context) |
|
281 |
|
282 |
|
283 @decorators.view |
|
284 def delete(request, page=None, partial_path=None, link_name=None, |
|
285 template=DEF_DOCS_EDIT_TMPL): |
|
286 """Request handler to delete Document Model entity. |
|
287 |
|
288 Args: |
|
289 request: the standard django request object |
|
290 page: a soc.logic.site.page.Page object which is abstraction that combines |
|
291 a Django view with sidebar menu info |
|
292 partial_path: the Document's site-unique "path" extracted from the URL, |
|
293 minus the trailing link_name |
|
294 link_name: the last portion of the Document's site-unique "path" |
|
295 extracted from the URL |
|
296 template: the "sibling" template (or a search list of such templates) |
|
297 from which to construct the public.html template name (or names) |
|
298 |
|
299 Returns: |
|
300 A subclass of django.http.HttpResponse which redirects |
|
301 to /site/docs/list. |
|
302 """ |
|
303 |
|
304 try: |
|
305 access.checkIsDeveloper(request) |
|
306 except soc.views.out_of_band.AccessViolationResponse, alt_response: |
|
307 # TODO(tlarsen): change this to just limit the Documents that can be |
|
308 # deleted by the User in their current Role |
|
309 return alt_response.response() |
|
310 |
|
311 # create default template context for use with any templates |
|
312 context = helper.responses.getUniversalContext(request) |
|
313 context['page'] = page |
|
314 |
|
315 existing_doc = None |
|
316 path = path_link_name.combinePath([partial_path, link_name]) |
|
317 |
|
318 # try to fetch Document entity corresponding to path if one exists |
|
319 try: |
|
320 if path: |
|
321 existing_doc = document.logic.getFromFields(partial_path=partial_path, |
|
322 link_name=link_name) |
|
323 except out_of_band.ErrorResponse, error: |
|
324 # show custom 404 page when path doesn't exist in Datastore |
|
325 error.message = error.message + DEF_CREATE_NEW_DOC_MSG |
|
326 return simple.errorResponse(request, page, error, template, context) |
|
327 |
|
328 if existing_doc: |
|
329 document.logic.delete(existing_doc) |
|
330 |
|
331 return http.HttpResponseRedirect('/docs/list') |