-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathadmin.py
1262 lines (1075 loc) · 56.6 KB
/
admin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
from django import forms
from django.conf import settings
from django.contrib import admin
from django.contrib.admin import helpers, widgets
from django.contrib.admin.util import unquote, flatten_fieldsets
from django.contrib.auth.admin import GroupAdmin
from django.contrib.auth.models import Group
from django.contrib.contenttypes import generic
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.db.models import get_model
from django.forms.formsets import all_valid
from django.forms.util import ErrorList
from django.http import Http404
from django.utils.decorators import method_decorator
from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext, ugettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from sorl.thumbnail.fields import ImageWithThumbnailsField
import os.path
from permissions.models import Role
from akvo.rsr.forms import PartnerSiteAdminForm
from akvo.rsr.mixins import TimestampsAdminDisplayMixin
from akvo.utils import get_rsr_limited_change_permission, permissions, custom_get_or_create_country
NON_FIELD_ERRORS = '__all__'
csrf_protect_m = method_decorator(csrf_protect)
admin.site.unregister(Group)
class RSRGroupAdmin(GroupAdmin):
list_display = GroupAdmin.list_display + (permissions,)
admin.site.register(Group, RSRGroupAdmin)
class PermissionAdmin(admin.ModelAdmin):
list_display = (u'__unicode__', u'content_type', )
list_filter = (u'content_type', )
ordering = (u'content_type', )
admin.site.register(get_model('auth', 'permission'), PermissionAdmin)
class CountryAdmin(admin.ModelAdmin):
list_display = (u'name', u'iso_code', u'continent', u'continent_code', )
list_filter = (u'continent', )
readonly_fields = (u'name', u'continent', u'continent_code')
def get_actions(self, request):
""" Remove delete admin action for "non certified" users"""
actions = super(CountryAdmin, self).get_actions(request)
opts = self.opts
if not request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()):
del actions['delete_selected']
return actions
def save_model(self, request, obj, form, change):
if obj.iso_code:
custom_get_or_create_country(obj.iso_code, obj)
def get_readonly_fields(self, request, obj=None):
if obj:
# return u'iso_code', u'name', u'continent', u'continent_code'
return u'name', u'continent', u'continent_code'
else:
return u'name', u'continent', u'continent_code'
admin.site.register(get_model('rsr', 'country'), CountryAdmin)
class RSR_LocationFormFormSet(forms.models.BaseInlineFormSet):
def clean(self):
if self.forms:
# keep track of how many non-deleted forms we have and how many primary locations are ticked
form_count = primary_count = 0
for form in self.forms:
if form.is_valid() and not form.cleaned_data.get('DELETE', False):
form_count += 1
primary_count += 1 if form.cleaned_data['primary'] else 0
# if we have any forms left there must be exactly 1 primary location
if form_count > 0 and not primary_count == 1:
self._non_form_errors = ErrorList([
_(u'The project must have exactly one primary location if any locations at all are to be included')
])
class OrganisationLocationInline(admin.StackedInline):
model = get_model('rsr', 'organisationlocation')
extra = 0
formset = RSR_LocationFormFormSet
class InternalOrganisationIDAdmin(admin.ModelAdmin):
list_display = (u'identifier', u'recording_org', u'referenced_org',)
search_fields = (u'identifier', u'recording_org__name', u'referenced_org__name',)
admin.site.register(get_model('rsr', 'internalorganisationid'), InternalOrganisationIDAdmin)
class OrganisationAdminForm(forms.ModelForm):
class Meta:
model = get_model('rsr', 'organisation')
def clean_iati_org_id(self):
return self.cleaned_data['iati_org_id'] or None
class OrganisationAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin):
# NOTE: The change_form.html template relies on the fieldsets to put the inline forms correctly.
# If the fieldsets are changed, the template may need fixing too
fieldsets = (
(_(u'General information'), {'fields': ('name', 'long_name', 'partner_types', 'organisation_type',
'new_organisation_type', 'logo', 'url', 'iati_org_id', 'language',
'content_owner',)}),
(_(u'Contact information'), {'fields': ('phone', 'mobile', 'fax', 'contact_person', 'contact_email', ), }),
(_(u'About the organisation'), {'fields': ('description', 'notes',)}),
)
form = OrganisationAdminForm
inlines = (OrganisationLocationInline,)
exclude = ('internal_org_ids',)
# note that readonly_fields is changed by get_readonly_fields()
# created_at and last_modified_at MUST be readonly since they have the auto_now/_add attributes
readonly_fields = ('partner_types', 'created_at', 'last_modified_at',)
list_display = ('name', 'long_name', 'website', 'language')
search_fields = ('name', 'long_name')
def get_actions(self, request):
""" Remove delete admin action for "non certified" users"""
actions = super(OrganisationAdmin, self).get_actions(request)
opts = self.opts
if not request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()):
del actions['delete_selected']
return actions
#Methods overridden from ModelAdmin (django/contrib/admin/options.py)
def __init__(self, model, admin_site):
"""
Override to add self.formfield_overrides.
Needed to get the ImageWithThumbnailsField working in the admin.
"""
self.formfield_overrides = {ImageWithThumbnailsField: {'widget': widgets.AdminFileWidget}, }
super(OrganisationAdmin, self).__init__(model, admin_site)
def allowed_partner_types(self, obj):
return ', '.join([pt.label for pt in obj.partner_types.all()])
def get_list_display(self, request):
# see the notes fields in the change list if you have the right permissions
if request.user.has_perm(self.opts.app_label + '.' + self.opts.get_change_permission()):
return list(self.list_display) + ['allowed_partner_types']
return super(OrganisationAdmin, self).get_list_display(request)
def get_readonly_fields(self, request, obj=None):
# parter_types is read only unless you have change permission for organisations
if not request.user.has_perm(self.opts.app_label + '.' + self.opts.get_change_permission()):
self.readonly_fields = ('partner_types', 'created_at', 'last_modified_at',)
# hack to set the help text
#try:
# field = [f for f in obj._meta.local_many_to_many if f.name == 'partner_types']
# if len(field) > 0:
# field[0].help_text = 'The allowed partner types for this organisation'
#except:
# pass
else:
self.readonly_fields = ('created_at', 'last_modified_at',)
# hack to set the help text
#try:
# if not obj is None:
# field = [f for f in obj._meta.local_many_to_many if f.name == 'partner_types']
# if len(field) > 0:
# field[0].help_text = 'The allowed partner types for this organisation. Hold down "Control", or "Command" on a Mac, to select more than one.'
#except:
# pass
return super(OrganisationAdmin, self).get_readonly_fields(request, obj=obj)
def queryset(self, request):
qs = super(OrganisationAdmin, self).queryset(request)
opts = self.opts
if request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()):
return qs
elif request.user.has_perm(opts.app_label + '.' + get_rsr_limited_change_permission(opts)):
organisation = request.user.get_profile().organisation
return qs.filter(pk=organisation.id)
else:
raise PermissionDenied
def has_change_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
If `obj` is None, this should return True if the given request has
permission to change *any* object of the given type.
get_rsr_limited_change_permission is used for partner orgs to limit their listing and editing to
"own" projects, organisation and user profiles
"""
opts = self.opts
if request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()):
return True
if request.user.has_perm(opts.app_label + '.' + get_rsr_limited_change_permission(opts)):
if obj:
return obj == request.user.get_profile().organisation
else:
return True
return False
admin.site.register(get_model('rsr', 'organisation'), OrganisationAdmin)
class OrganisationAccountAdmin(admin.ModelAdmin):
list_display = (u'organisation', u'account_level', )
admin.site.register(get_model('rsr', 'organisationaccount'), OrganisationAccountAdmin)
#class LinkAdmin(admin.ModelAdmin):
# list_display = ('url', 'caption', 'show_link', )
class LinkInline(admin.TabularInline):
model = get_model('rsr', 'link')
extra = 3
list_display = ('url', 'caption', 'show_link')
def partner_clean(obj, field_name='organisation'):
"""
this function figures out if a given user's organisation is a partner in some function
associated with the current project. This is to avoid the situation where a user
who is a partner admin creates a project without the own org as a partner
resulting in a project that can't be edited by that user or anyone else form the org.
params:
obj: a formset for one of the partner types
field_name: the filed name of the foreign key field that points to the org
"""
user_profile = obj.request.user.get_profile()
# superusers can do whatever they like!
if obj.request.user.is_superuser:
found = True
# if the user is a partner org we try to avoid foot shooting
elif user_profile.get_is_org_admin() or user_profile.get_is_org_editor():
my_org = user_profile.organisation
found = False
for i in range(0, obj.total_form_count()):
form = obj.forms[i]
try:
form_org = form.cleaned_data[field_name]
if not form.cleaned_data.get('DELETE', False) and my_org == form_org:
# found our own org, all is well move on!
found = True
break
except:
pass
else:
found = True
try:
#obj instance is the Project instance. We use it to store the info about
#wether we have found our own org in the found attribute.
if not obj.instance.found:
obj.instance.found = found
except AttributeError:
obj.instance.found = found
try:
# add the formset to attribute partner_formsets. This is to conveniently
# be able to dig up these formsets later for error assignment
obj.instance.partner_formsets
except AttributeError:
obj.instance.partner_formsets = []
obj.instance.partner_formsets.append(obj)
#class RSR_FundingPartnerInlineFormFormSet(forms.models.BaseInlineFormSet):
# # do cleaning looking for the user's org in the funding partner forms
# def clean(self):
# partner_clean(self, 'funding_organisation')
#
#class FundingPartnerInline(admin.TabularInline):
# model = get_model('rsr', 'fundingpartner')
# extra = 1
# # put custom formset in chain of inheritance. the formset creation ends up
# # returning a formset of type FundingPartnerFormForm (I think...) but the
# # RSR_FundingPartnerInlineFormFormSet is a parent to it and thus we can access
# # the custom clean()
# formset = RSR_FundingPartnerInlineFormFormSet
#
# def get_formset(self, request, *args, **kwargs):
# formset = super(FundingPartnerInline, self).get_formset(request, *args, **kwargs)
# formset.request = request
# return formset
#see above
class RSR_FieldPartnerInlineFormFormSet(forms.models.BaseInlineFormSet):
def clean(self):
partner_clean(self, 'field_organisation')
class FieldPartnerInline(admin.TabularInline):
model = get_model('rsr', 'fieldpartner')
extra = 1
formset = RSR_FieldPartnerInlineFormFormSet
def get_formset(self, request, *args, **kwargs):
formset = super(FieldPartnerInline, self).get_formset(request, *args, **kwargs)
formset.request = request
return formset
#see above
class RSR_SupportPartnerInlineFormFormSet(forms.models.BaseInlineFormSet):
def clean(self):
partner_clean(self, 'support_organisation')
class SupportPartnerInline(admin.TabularInline):
model = get_model('rsr', 'supportpartner')
extra = 1
formset = RSR_SupportPartnerInlineFormFormSet
def get_formset(self, request, *args, **kwargs):
formset = super(SupportPartnerInline, self).get_formset(request, *args, **kwargs)
formset.request = request
return formset
#see above
class RSR_SponsorPartnerInlineFormFormSet(forms.models.BaseInlineFormSet):
def clean(self):
partner_clean(self, 'sponsor_organisation')
class SponsorPartnerInline(admin.TabularInline):
model = get_model('rsr', 'sponsorpartner')
extra = 1
formset = RSR_SponsorPartnerInlineFormFormSet
def get_formset(self, request, *args, **kwargs):
formset = super(SponsorPartnerInline, self).get_formset(request, *args, **kwargs)
formset.request = request
return formset
class BudgetItemLabelAdmin(admin.ModelAdmin):
list_display = (u'label',)
admin.site.register(get_model('rsr', 'budgetitemlabel'), BudgetItemLabelAdmin)
class BudgetItemAdminInLine(admin.TabularInline):
model = get_model('rsr', 'budgetitem')
extra = 1
class Media:
css = {'all': (os.path.join(settings.MEDIA_URL, 'akvo/css/src/rsr_admin.css').replace('\\', '/'),)}
js = (os.path.join(settings.MEDIA_URL, 'akvo/js/src/rsr_admin.js').replace('\\', '/'),)
#admin.site.register(get_model('rsr', 'budgetitem'), BudgetItemAdminInLine)
class BudgetAdminInLine(admin.TabularInline):
model = get_model('rsr', 'budget')
class PublishingStatusAdmin(admin.ModelAdmin):
list_display = (u'project', u'status', )
search_fields = ('project__title', 'status', )
list_filter = ('status', )
admin.site.register(get_model('rsr', 'publishingstatus'), PublishingStatusAdmin)
#class ProjectAdminForm(forms.ModelForm):
# class Meta:
# model = get_model('rsr', 'project')
#
# def clean(self):
# return self.cleaned_data
#
#admin.site.register(get_model('rsr', 'location'))
class FocusAreaAdmin(admin.ModelAdmin):
model = get_model('rsr', 'FocusArea')
list_display = ('name', 'slug', 'image',)
admin.site.register(get_model('rsr', 'FocusArea'), FocusAreaAdmin)
class BenchmarknameInline(admin.TabularInline):
model = get_model('rsr', 'Category').benchmarknames.through
extra = 3
class CategoryAdmin(admin.ModelAdmin):
model = get_model('rsr', 'Category')
list_display = ('name', 'focus_areas_html', 'category_benchmarks_html', )
admin.site.register(get_model('rsr', 'Category'), CategoryAdmin)
class BenchmarknameAdmin(admin.ModelAdmin):
model = get_model('rsr', 'Benchmarkname')
list_display = ('name', 'order',)
admin.site.register(get_model('rsr', 'Benchmarkname'), BenchmarknameAdmin)
class MiniCMSAdmin(admin.ModelAdmin):
model = get_model('rsr', 'MiniCMS')
list_display = ('__unicode__', 'active', )
admin.site.register(get_model('rsr', 'MiniCMS'), MiniCMSAdmin)
class BenchmarkInline(admin.TabularInline):
model = get_model('rsr', 'benchmark')
# only show the value, category and benchmark are not to be edited here
fields = ('value',)
extra = 0
max_num = 0
class GoalInline(admin.TabularInline):
model = get_model('rsr', 'goal')
fields = ('text',)
extra = 3
max_num = 8
class RSR_PartnershipInlineFormFormSet(forms.models.BaseInlineFormSet):
def clean(self):
def duplicates_in_list(seq):
"return True if the list contains duplicate items"
seq_set = list(set(seq))
# if the set isn't of the same length as the list there must be dupes in the list
return len(seq) != len(seq_set)
user = self.request.user
user_profile = user.get_profile()
errors = []
# superusers can do whatever they like!
if user.is_superuser:
my_org_found = True
# if the user is a partner org we try to avoid foot shooting
elif user_profile.get_is_org_admin() or user_profile.get_is_org_editor():
my_org = user_profile.organisation
my_org_found = False
for form in self.forms:
try:
form_org = form.cleaned_data['organisation']
if not form.cleaned_data.get('DELETE', False) and my_org == form_org:
# found our own org, all is well move on!
my_org_found = True
break
except:
pass
else:
my_org_found = True
if not my_org_found:
errors += [_(u'Your organisation should be somewhere here.')]
# now check that the same org isn't assigned the same partner_type more than once
partner_types = {}
for form in self.forms:
# populate a dict with org names as keys and a list of partner_types as values
try:
if not form.cleaned_data.get('DELETE', False):
partner_types.setdefault(form.cleaned_data['organisation'], []).append(form.cleaned_data['partner_type'])
except:
pass
for org, types in partner_types.items():
# are there duplicates in the list of partner_types?
if duplicates_in_list(types):
errors += [_(u'{org} has duplicate partner types of the same kind.'.format(org=org))]
self._non_form_errors = ErrorList(errors)
class RSR_PartnershipInlineForm(forms.ModelForm):
class Meta:
model = get_model('rsr', 'Partnership')
def clean_partner_type(self):
partner_types = get_model('rsr', 'PartnerType').objects.all()
partner_types_dict = {partner_type.id: partner_type.label for partner_type in partner_types}
allowed = [partner_type.pk for partner_type in self.cleaned_data['organisation'].partner_types.all()]
data = self.cleaned_data['partner_type']
if data not in allowed:
raise forms.ValidationError("{org} is not allowed to be a {partner_type_label}".format(
org=self.cleaned_data['organisation'],
partner_type_label=partner_types_dict[data]
))
return data
class PartnershipInline(admin.TabularInline):
model = get_model('rsr', 'Partnership')
extra = 1
form = RSR_PartnershipInlineForm
formset = RSR_PartnershipInlineFormFormSet
def get_formset(self, request, *args, **kwargs):
"Add the request to the formset for use in RSR_PartnershipInlineFormFormSet.clean()"
formset = super(PartnershipInline, self).get_formset(request, *args, **kwargs)
formset.request = request
return formset
class ProjectLocationInline(admin.StackedInline):
model = get_model('rsr', 'projectlocation')
extra = 0
formset = RSR_LocationFormFormSet
class ProjectAdmin(TimestampsAdminDisplayMixin, admin.ModelAdmin):
model = get_model('rsr', 'project')
inlines = (
GoalInline, ProjectLocationInline, BudgetItemAdminInLine, BenchmarkInline, PartnershipInline, LinkInline,
)
save_as = True
# fieldsets = (
# (_(u'Project description'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'Give your project a short title and subtitle in RSR. These fields are the '
# u'newspaper headline for your project: use them to attract attention to what you are doing.'
# ),
# 'fields': ('title', 'subtitle', 'status', 'language',),
# }),
# (_(u'Categories'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'Please select all categories applicable to your project. '
# u'(The Focus area(s) of each category is shown in parenthesis after the category name)'
# ),
# 'fields': (('categories',)),
# }),
# (_(u'Project info'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'The summary should <em>briefly</em> explain why the project is being carried out, '
# u'where it is taking place, who will benefit and/or participate, what it specifically '
# u'hopes to accomplish and how those specific goals will be accomplished.'
# ),
# 'fields': ('project_plan_summary', 'current_image', 'current_image_caption', )
# }),
# (_(u'Project details'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'In-depth information about your project should be put in this section. '
# u'Use the Background, Project plan, Current status and Sustainability fields '
# u'to tell people more about the project.'
# ),
# 'fields': ('background', 'project_plan', 'current_status', 'sustainability', ),
# }),
# (_(u'Project meta info'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'The project meta information fields are not public. '
# u'They allow you to make notes to other members of your organisation or '
# u'partners with access to your projects on the RSR Admin pages.'
# ),
# 'fields': ('project_rating', 'notes', ),
# }),
# (_(u'Project budget'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'The request posted date is filled in for you automatically when you create a project. '
# u'When the project implementation phase is complete, enter the <em>Date complete</em> here.'
# ),
# 'fields': ('currency', 'date_request_posted', 'date_complete', ),
# }),
# (_(u'Aggregates'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _('Aggregate financial data'),
# 'fields': (('funds', 'funds_needed',), ),
# }),
# (_(u'Goals'), {
# 'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
# u'Describe what the project hopes to accomplish. Keep in mind the SMART criteria: '
# u'Specific, Measurable, Agreed upon, Realistic and Time-specific. '
# u'The numbered fields can be used to list specific goals whose accomplishment '
# u'will be used to measure overall project success.'
# ),
# 'fields': ('goals_overview', )
# }),
# )
fieldsets = (
(_(u'General Information'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'This section should contain the top-level information about your project which will be publicly available and used within searches. Try to keep your Title and Subtitle short and snappy.'
),
'fields': ('title', 'subtitle', 'status', 'language', 'date_request_posted', 'date_complete',),
}),
(_(u'Description'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Here you can complete the in-depth descriptive details regarding your project, its history and plans for the future. Both The Project Plan and Sustainability fields are unlimited, so you can add additional details to your project there.'
),
'fields': ('project_plan_summary', 'background', 'current_status', 'project_plan', 'sustainability', 'target_group',),
}),
(_(u'Goals'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Here you should be entering information about what your project intends to achieve through its implementation.'
),
'fields': ('goals_overview',),
}),
(_(u'Photo'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Please upload a photo that displays an interesting part of your project, plan or implementation.'
),
'fields': ('current_image', 'current_image_caption', 'current_image_credit'),
}),
(_(u'Locations'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Here you can add the physical locations where the project is being carried out. These will appear on the map on your project page. Use the link to iTouchMap.com to obtain the Coordinates for your locations, as these are the items that ensure the pin is in the correct place.'
),
'fields': (),
}),
(_(u'Budget'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Please enter the details of what your project will be spending its available funds on.'
),
'fields': ('currency', ),
}),
(_(u'Project Focus'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Here you can choose the Categories, & Focus Areas that your project is operating in. Once you have selected those, Save your project and you will then be presented with a list of Benchmarks applicable to your fields which you can define for measuring the success of your project.'
),
'fields': ('categories',),
}),
(_(u'Partners'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'Add each of the partners you are working with on your project. These organisations must be existing already in Akvo RSR. If you are working with a Partner that does not exist in the system, please send the details of the organisation including Name, Address, Logo, Contact Person and Website to [email protected].'
),
'fields': (),
}),
(_(u'Additional Information'), {
'description': u'<p style="margin-left:0; padding-left:0; margin-top:1em; width:75%%;">%s</p>' % _(
u'You can add links to your project such as Organisation Websites or Akvopedia articles containing relevant information to improve the information available to viewers of your project. You can also make notes on the project. These notes are only visible within this Admin so can be used to identify missing information, specific contact details or status changes that you do not want to be visible on your project page.'
),
'fields': ('notes',),
}),
)
list_display = ('id', 'title', 'status', 'project_plan_summary', 'latest_update', 'show_current_image', 'is_published',)
search_fields = ('title', 'status', 'project_plan_summary', 'partnerships__internal_id')
list_filter = ('currency', 'status', )
# created_at and last_modified_at MUST be readonly since they have the auto_now/_add attributes
readonly_fields = ('budget', 'funds', 'funds_needed', 'created_at', 'last_modified_at',)
#form = ProjectAdminForm
def get_actions(self, request):
""" Remove delete admin action for "non certified" users"""
actions = super(ProjectAdmin, self).get_actions(request)
opts = self.opts
if not request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()):
del actions['delete_selected']
return actions
#Methods overridden from ModelAdmin (django/contrib/admin/options.py)
def __init__(self, model, admin_site):
"""
Override to add self.formfield_overrides.
Needed to get the ImageWithThumbnailsField working in the admin.
"""
self.formfield_overrides = {ImageWithThumbnailsField: {'widget': widgets.AdminFileWidget}, }
super(ProjectAdmin, self).__init__(model, admin_site)
def queryset(self, request):
"""
Return a queryset possibly filtered depending on current user's group(s)
"""
qs = super(ProjectAdmin, self).queryset(request)
opts = self.opts
if request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()):
return qs
elif request.user.has_perm(opts.app_label + '.' + get_rsr_limited_change_permission(opts)):
projects = request.user.get_profile().organisation.all_projects()
#projects = get_model('rsr', 'organisation').projects.filter(pk__in=[request.user.get_profile().organisation.pk])
return qs.filter(pk__in=projects)
else:
raise PermissionDenied
def has_change_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
If `obj` is None, this should return True if the given request has
permission to change *any* object of the given type.
get_rsr_limited_change_permission is used for partner orgs to limit their listing and editing to
"own" projects, organisation and user profiles
"""
opts = self.opts
if request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()):
return True
if request.user.has_perm(opts.app_label + '.' + get_rsr_limited_change_permission(opts)):
projects = request.user.get_profile().organisation.all_projects()
#projects = get_model('rsr', 'organisation').projects.filter(pk__in=[request.user.get_profile().organisation.pk])
if obj:
return obj in projects
else:
return True
return False
@csrf_protect_m
@transaction.commit_on_success
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
opts = model._meta
if not self.has_add_permission(request):
raise PermissionDenied
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
# add to add_view() from django 1.4
# check if we're trying to create a new project by copying an existing one. If so we ignore
# location and benchmark inlines
if not "_saveasnew" in request.POST or not prefix in ['benchmarks', 'rsr-location-content_type-object_id']:
# end of add although the following block is indented as a result
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, False)
self.save_related(request, form, formsets, False)
self.log_addition(request, new_object)
return self.response_add(request, new_object)
else:
# Prepare the dict of initial data from the request.
# We have to special-case M2Ms as a list of comma-separated PKs.
initial = dict(request.GET.items())
for k in initial:
try:
f = opts.get_field(k)
except models.FieldDoesNotExist:
continue
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
# hack by GvH to get user's organisation preset as partner when adding a new project
if prefix == 'partnerships':
formset = FormSet(instance=self.model(), prefix=prefix,
initial=[{'organisation': request.user.get_profile().organisation}],
queryset=inline.queryset(request))
else:
formset = FormSet(instance=self.model(), prefix=prefix,
queryset=inline.queryset(request))
# end hack
formsets.append(formset)
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
self.get_prepopulated_fields(request),
self.get_readonly_fields(request),
model_admin=self)
media = self.media + adminForm.media
inline_admin_formsets = []
for inline, formset in zip(inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request))
readonly = list(inline.get_readonly_fields(request))
prepopulated = dict(inline.get_prepopulated_fields(request))
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
fieldsets, prepopulated, readonly, model_admin=self)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
context = {
'title': _('Add %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
'is_popup': "_popup" in request.REQUEST,
'show_delete': False,
'media': media,
'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets),
'app_label': opts.app_label,
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
# benchmark change, budgetitem all, goal all, location all,
# @csrf_protect_m
# @transaction.commit_on_success
# def change_view(self, request, object_id, extra_context=None):
# "The 'change' admin view for this model."
# model = self.model
# opts = model._meta
#
# obj = self.get_object(request, unquote(object_id))
#
# if not self.has_change_permission(request, obj):
# raise PermissionDenied
#
# if obj is None:
# raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
#
# if request.method == 'POST' and "_saveasnew" in request.POST:
# return self.add_view(request, form_url='../add/')
#
# ModelForm = self.get_form(request, obj)
# formsets = []
# if request.method == 'POST':
# form = ModelForm(request.POST, request.FILES, instance=obj)
# if form.is_valid():
# form_validated = True
# new_object = self.save_form(request, form, change=True)
# else:
# form_validated = False
# new_object = obj
# prefixes = {}
# for FormSet, inline in zip(self.get_formsets(request, new_object),
# self.inline_instances):
# prefix = FormSet.get_default_prefix()
# prefixes[prefix] = prefixes.get(prefix, 0) + 1
# if prefixes[prefix] != 1:
# prefix = "%s-%s" % (prefix, prefixes[prefix])
# formset = FormSet(request.POST, request.FILES,
# instance=new_object, prefix=prefix,
# queryset=inline.queryset(request))
#
# formsets.append(formset)
# if all_valid(formsets) and form_validated:
# self.save_model(request, new_object, form, change=True)
# form.save_m2m()
# for formset in formsets:
# self.save_formset(request, form, formset, change=True)
#
# change_message = self.construct_change_message(request, form, formsets)
# self.log_change(request, new_object, change_message)
# return self.response_change(request, new_object)
#
# else:
# form = ModelForm(instance=obj)
# prefixes = {}
# for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
# prefix = FormSet.get_default_prefix()
# prefixes[prefix] = prefixes.get(prefix, 0) + 1
# if prefixes[prefix] != 1:
# prefix = "%s-%s" % (prefix, prefixes[prefix])
# formset = FormSet(instance=obj, prefix=prefix,
# queryset=inline.queryset(request))
# formsets.append(formset)
#
# adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
# self.prepopulated_fields, self.get_readonly_fields(request, obj),
# model_admin=self)
# media = self.media + adminForm.media
#
# inline_admin_formsets = []
# for inline, formset in zip(self.inline_instances, formsets):
# fieldsets = list(inline.get_fieldsets(request, obj))
# readonly = list(inline.get_readonly_fields(request, obj))
# inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets, readonly, model_admin=self)
# inline_admin_formsets.append(inline_admin_formset)
# media = media + inline_admin_formset.media
#
# context = {
# 'title': _(u'Change project'),
# 'adminform': adminForm,
# 'object_id': object_id,
# 'original': obj,
# 'is_popup': "_popup" in request.REQUEST,
# 'media': mark_safe(media),
# 'inline_admin_formsets': inline_admin_formsets,
# 'errors': helpers.AdminErrorList(form, formsets),
# 'root_path': self.admin_site.root_path,
# 'app_label': opts.app_label,
# }
# context.update(extra_context or {})
# return self.render_change_form(request, context, change=True, obj=obj)
admin.site.register(get_model('rsr', 'project'), ProjectAdmin)
class SmsReporterInline(admin.TabularInline):
model = get_model('rsr', 'smsreporter')
extra = 1
def get_readonly_fields(self, request, obj=None):
""" Only allow viewing of gateway number and project for non-superusers
"""
#opts = self.opts
user = request.user
if not user.is_superuser:
self.readonly_fields = ('gw_number', 'project',)
else:
self.readonly_fields = ()
return super(SmsReporterInline, self).get_readonly_fields(request, obj)
def formfield_for_dbfield(self, db_field, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
Added by GvH:
Use hook to implement limits to project list select for org users.
"""
request = kwargs.get("request", None)
# Limit the choices of the project db_field to projects linked to user's org
# if we have an org user
if db_field.attname == 'project_id':
opts = self.opts
user = request.user
if user.has_perm(opts.app_label + '.' + get_rsr_limited_change_permission(opts)):
db_field.rel.limit_choices_to = {'pk__in': user.get_profile().organisation.all_projects()}
return super(SmsReporterInline, self).formfield_for_dbfield(db_field, **kwargs)
class UserProfileAdminForm(forms.ModelForm):
"""
This form displays two extra fields that show if the user belongs to the groups
GROUP_RSR_PARTNER_ADMINS and/or GROUP_RSR_PARTNER_EDITORS.
"""
class Meta:
model = get_model('rsr', 'userprofile')
is_active = forms.BooleanField(required=False, label=_(u'account is active'),)
is_org_admin = forms.BooleanField(required=False, label=_(u'organisation administrator'),)
is_org_editor = forms.BooleanField(required=False, label=_(u'organisation project editor'),)
is_sms_updater = forms.BooleanField(required=False, label=_(u'can create sms updates',),)
def __init__(self, *args, **kwargs):
initial_data = {}
instance = kwargs.get('instance', None)
if instance:
initial_data['is_active'] = instance.get_is_active()
initial_data['is_org_admin'] = instance.get_is_org_admin()
initial_data['is_org_editor'] = instance.get_is_org_editor()
initial_data['is_sms_updater'] = instance.has_perm_add_sms_updates()
kwargs.update({'initial': initial_data})
super(UserProfileAdminForm, self).__init__(*args, **kwargs)
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'organisation', 'get_is_active', 'get_is_org_admin', 'get_is_org_editor', 'has_perm_add_sms_updates', 'latest_update_date',)
search_fields = ('user__username', 'organisation__name', 'organisation__long_name',)
list_filter = ('organisation',)
ordering = ("user__username",)
inlines = [SmsReporterInline, ]
form = UserProfileAdminForm
def get_actions(self, request):
""" Remove delete admin action for "non certified" users"""
actions = super(UserProfileAdmin, self).get_actions(request)
opts = self.opts
if not request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()):
del actions['delete_selected']
return actions
#Methods overridden from ModelAdmin (django/contrib/admin/options.py)
def get_form(self, request, obj=None, **kwargs):
# non-superusers don't get to see it all
if not request.user.is_superuser:
# hide sms-related stuff
self.exclude = ('phone_number', 'validation',)
# user and org are only shown as text, not select widget
#self.readonly_fields = ('user', 'organisation',)
# this is needed to remove some kind of caching on exclude and readonly_fk,
# resulting in the above fields being hidden/changed from superusers after
# a vanilla user has accessed the form!
else:
self.exclude = None
#self.readonly_fields = ()
form = super(UserProfileAdmin, self).get_form(request, obj, **kwargs)
if not request.user.is_superuser and obj.validation != obj.VALIDATED:
self.inlines = []