--- /dev/null
+This app exists explicitly to make sure it's loaded first. That way,
+putting monkey patching and other evil things in the __init__.py for
+it should work.
--- /dev/null
+import types
+
+from django.contrib import admin
+
+from postgresqleu.util.forms import ConcurrentProtectedModelForm
+
+#
+# Inject the ConcurrentProjectedModelForm into all ModelAdmins that don't
+# explicitly specify override it. Do this by patching out the meaning
+# of admin.ModelAdmin to be our own form which inherits from it.
+#
+class ConcurrentInjectedAdmin(admin.ModelAdmin):
+ form = ConcurrentProtectedModelForm
+
+admin.ModelAdmin = ConcurrentInjectedAdmin
+
+
+
+#
+# Define our own handling of registering a model for admin without
+# it's own class. The default is to set admin_class to ModelAdmin
+# in that case, so we just do it one step early in order to use our
+# own injected model from above.
+#
+_oldreg = admin.site.register
+def _concurrent_injected_register(self, model_or_iterable, admin_class=None, **options):
+ if admin_class is None:
+ admin_class = ConcurrentInjectedAdmin
+ return _oldreg(model_or_iterable, admin_class, **options)
+
+admin.site.register = types.MethodType(_concurrent_injected_register, admin.AdminSite)
+
from postgresqleu.accountinfo.lookups import UserLookup
from postgresqleu.confreg.lookups import RegistrationLookup
from postgresqleu.util.admin import SelectableWidgetAdminFormMixin
+from postgresqleu.util.forms import ConcurrentProtectedModelForm
from util import notify_reg_confirmed
#
# General admin classes
#
-class ConferenceAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class ConferenceAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = Conference
exclude = []
list_display = ('conferencename', 'active', 'callforpapersopen', 'callforsponsorsopen', 'feedbackopen', 'startdate', 'enddate')
ordering = ('-startdate', )
-class ConferenceRegistrationForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class ConferenceRegistrationForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = ConferenceRegistration
exclude = []
else:
return False
-class ConferenceSessionForm(forms.ModelForm):
+class ConferenceSessionForm(ConcurrentProtectedModelForm):
class Meta:
model = ConferenceSession
exclude = []
list_filter = ['conference', ]
ordering = ['conference', 'day', ]
-class RegistrationTypeAdminForm(forms.ModelForm):
+class RegistrationTypeAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = RegistrationType
exclude = []
class ShirtsizeAdmin(admin.ModelAdmin):
list_display = ['shirtsize', 'sortkey', ]
-class ConferenceAdditionalOptionAdminForm(forms.ModelForm):
+class ConferenceAdditionalOptionAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = ConferenceAdditionalOption
exclude = []
return inst.confirmed_count + inst.unconfirmed_count
used_count.short_description = 'Total used'
-class SpeakerAdminForm(forms.ModelForm):
+class SpeakerAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = Speaker
exclude = []
extra = 0
can_delete = False
-class PrepaidBatchAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class PrepaidBatchAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = PrepaidBatch
exclude = []
return inst.used
used_num.short_description = 'Used vouchers'
-class PrepaidVoucherAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class PrepaidVoucherAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = PrepaidVoucher
exclude = []
return "%s %s" % (obj.user.firstname, obj.user.lastname)
return None
-class DiscountCodeAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class DiscountCodeAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = DiscountCode
exclude = []
list_filter = ['conference', ]
form = DiscountCodeAdminForm
-class BulkPaymentAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class BulkPaymentAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = BulkPayment
exclude = []
list_display = ['adminstring', 'conference', 'user', 'numregs', 'paidat', 'ispaid',]
list_filter = ['conference', ]
-class AttendeeMailAdminForm(forms.ModelForm):
+class AttendeeMailAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = AttendeeMail
exclude = []
form = AttendeeMailAdminForm
filter_horizontal = ('regclasses', )
-class PendingAdditionalOrderAdminForm(forms.ModelForm):
+class PendingAdditionalOrderAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = PendingAdditionalOrder
exclude = []
form = PendingAdditionalOrderAdminForm
list_display = ('reg', 'createtime', 'payconfirmedat')
-
-class VolunteerSlotAdminForm(forms.ModelForm):
+class VolunteerSlotAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = VolunteerSlot
exclude = []
from selectable.forms.widgets import AutoCompleteSelectMultipleWidget
from postgresqleu.accountinfo.lookups import UserLookup
from postgresqleu.util.admin import SelectableWidgetAdminFormMixin
+from postgresqleu.util.forms import ConcurrentProtectedModelForm
from models import SponsorshipContract, SponsorshipLevel, Sponsor
from models import SponsorshipBenefit, SponsorClaimedBenefit
extra = 1
formset = SponsorshipBenefitInlineFormset
-class SponsorshipLevelForm(forms.ModelForm):
+class SponsorshipLevelForm(ConcurrentProtectedModelForm):
class Meta:
model = SponsorshipLevel
exclude = []
return HttpResponseRedirect("/admin/confsponsor/sponsorshiplevel/{0}/copy".format(source_level[0].id))
copy_sponsorshiplevel.short_description = "Copy sponsorship level"
-class SponsorAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class SponsorAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = Sponsor
exclude = []
from selectable.forms.widgets import AutoCompleteSelectWidget, AutoCompleteSelectMultipleWidget
from postgresqleu.confreg.lookups import RegistrationLookup
from postgresqleu.util.admin import SelectableWidgetAdminFormMixin
+from postgresqleu.util.forms import ConcurrentProtectedModelForm
from postgresqleu.confreg.models import Conference, ConferenceRegistration, RegistrationType
from models import Wikipage, WikipageHistory, WikipageSubscriber
from models import AttendeeSignup
-class WikipageAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class WikipageAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = Wikipage
exclude = []
form = WikipageAdminForm
inlines = [WikipageHistoryInline, WikipageSubscriberInline]
-class AttendeeSignupAdminForm(forms.ModelForm):
+class AttendeeSignupAdminForm(ConcurrentProtectedModelForm):
class Meta:
model = AttendeeSignup
exclude = []
from selectable.forms.widgets import AutoCompleteSelectWidget
from postgresqleu.accountinfo.lookups import UserLookup
from postgresqleu.util.admin import SelectableWidgetAdminFormMixin
+from postgresqleu.util.forms import ConcurrentProtectedModelForm
from models import Invoice, InvoiceLog, InvoiceProcessor, InvoicePaymentMethod
from models import InvoiceRefund, VatRate
-class InvoiceAdminForm(SelectableWidgetAdminFormMixin, forms.ModelForm):
+class InvoiceAdminForm(SelectableWidgetAdminFormMixin, ConcurrentProtectedModelForm):
class Meta:
model = Invoice
exclude = []
if "processor" in self.changed_data:
raise ValidationError("Sorry, we never allow editing of the processor!")
return self.cleaned_data['processor']
- def clean(self):
- return self.cleaned_data
class InvoiceAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'recipient_name', 'total_amount', 'ispaid')
'django_markwhat',
'django.contrib.staticfiles',
'django.contrib.humanize',
+ 'postgresqleu._initial',
'postgresqleu.selectable',
'postgresqleu.static',
'postgresqleu.countries',
--- /dev/null
+from django import forms
+from django.forms import ValidationError
+from django.core.signing import Signer, BadSignature
+
+import cPickle
+import base64
+
+
+class _ValidatorField(forms.Field):
+ required=True
+ widget=forms.HiddenInput
+
+class ConcurrentProtectedModelForm(forms.ModelForm):
+ _validator = _ValidatorField()
+
+ def _filter_initial(self):
+ # self.initial will include things given in the URL after ?, so filter it to
+ # inly include items that are actually form fields.
+ return {k:v for k,v in self.initial.items() if k in self.fields.keys()}
+
+ def __init__(self, *args, **kwargs):
+ r = super(ConcurrentProtectedModelForm, self).__init__(*args, **kwargs)
+
+ self.fields['_validator'].initial = Signer().sign(base64.urlsafe_b64encode(cPickle.dumps(self._filter_initial(), -1)))
+
+ return r
+
+ def clean(self):
+ # Process the form itself
+ data = super(ConcurrentProtectedModelForm, self).clean()
+
+ # Fetch the list of values from the currernt object in the db
+ i = self._filter_initial()
+ try:
+ s = Signer().unsign(self.cleaned_data['_validator'])
+ b = base64.urlsafe_b64decode(s.encode('utf8'))
+ d = cPickle.loads(b)
+ for k,v in d.items():
+ if i[k] != v:
+ raise ValidationError("Concurrent modification of field {0}. Please reload the form and try again.".format(k))
+ except BadSignature:
+ raise ValidationError("Form has been tampered with!")
+ except TypeError:
+ raise ValidationError("Bad serialized form state")
+ except cPickle.UnpicklingError:
+ raise ValidationError("Bad serialized python form state")
+
+ return data