from damagelog.da.models import * from django.contrib import admin import datetime from django.forms.models import model_to_dict, ModelFormMetaclass from django.db import models from django import forms #import pdb # making auto-set display-only fields requires tweaking the admin app in three places: # 1. on unbound forms ('add' forms), have the form display an initial value equal to (username) or similar to (datetime) # what will be auto-added to the tuple. # 2. when a bound form comes back, whether it was add or change, remove the display-only fields so that the user can't # change them. # 3. in the model (datetime) or here (username), initialize the field to our computed value before saving, iff the object is new. class DisplayOnlyFormFriend(object): def __init__(self, *args, **kwargs): # STEP 1 if not 'instance' in kwargs or kwargs['instance'] is None: if not 'initial' in kwargs: kwargs['initial'] = {} kwargs['initial'].update(self.class_initial) super(DisplayOnlyFormFriend, self).__init__(*args, **kwargs) def clean(self): # STEP 2 # clean method for forms is a hook, and is empty in Form, but ModelForm does use the hook so it's not empty. # the cleaning is provoked by retrieving the errors property. # ISTR some clean methods saying 'pass', so we ignore the return value super(DisplayOnlyFormFriend, self).clean() # pdb.set_trace() for displayonly in self.class_initial.iterkeys(): del self.cleaned_data[displayonly] return self.cleaned_data class DefaultWhoWhenInline(object): # FormSets are more ambitious in their use of the has_changed() field than normal single-main-object add views # because they display an empty form and then *implicitly add* its object if you change anything, or else *ignore you* if you # don't change anything. so, it's suddenly really important to answer has_changed() correctly for this reason. # (even things like field validation don't happen if you haven't()changed, but the most directly important thing is # the implicit add) # # remember that django is a generation 2 webapp (light on javurscript), not gen3, so all these functions we write are # stateless from one http request to another. Displaying a form and then submitting it is TWO requests, so # all this form crap gets instantiated twice within what feels like one transaction (adding an object). actually it # is three times: once to GET the blank form, once to POST the answer and get a 302, then once to GET the # confirmation-or-error page. consequently the 'initial' value of a time field is going to be different between # the first GET and the second POST, and the blank forms within the formset will appear to have changed. # show_hidden_initial is meant just for this: the ``show'' means show it in the template, to the web browser, in # the step 1 GET phase; obviously it's ``hidden'' so it's never shown to the user. when show_hidden_initial=False # the ``hidden initial'' field does not exist: it's presumed we can re-fill the initial dict from the database state # identically across the first and second requests. for example I might have called it generate_hidden_initial, # use_hidden_initial, render_hidden_initial. but whatever, RTFS. formfield_overrides = { models.DateTimeField: { 'form_class': forms.SplitDateTimeField, 'widget': admin.widgets.AdminSplitDateTime, 'show_hidden_initial': True, }, } def get_formset(self, request, obj=None, **kwargs): Formsetclass = super(DefaultWhoWhenInline, self).get_formset(request, obj, **kwargs) # Formsets have an 'initial' too, but it's an array, for each form in the set. # we want to apply to all unbound forms in the set. (which will be the 1 extra form) emptymodel = self.model() emptymodel.enter_who = request.user emptymodel.enter_when = datetime.datetime.today() closure_initial = { 'enter_who': None, 'enter_when': None, } closure_initial = model_to_dict(emptymodel, fields=closure_initial) # STEP 1, STEP 2 class FormCurryClass(DisplayOnlyFormFriend): class_initial = closure_initial Formsetclass.form = type.__new__(ModelFormMetaclass, "ephemeral%s" % Formsetclass.form.__name__, (FormCurryClass, Formsetclass.form,), {}) closure_user = request.user class FormsetCurryClass(object): # we don't override save_existing, and that's how we make sure it's only for new forms within the formset. # obj.enter_when updated in models.py because it doesn't need the request object def save_new(self, form, commit=True): obj = super(FormsetCurryClass, self).save_new(form=form, commit=False) # STEP 3 (or, half of it) obj.enter_who = closure_user # unfortunately needs some paste-and-rape from django/forms/models.py BaseInlineFormset().save_new if commit: obj.save() if commit and hasattr(form, 'save_m2m'): form.save_m2m() # end paste-and-rape return obj Formsetclass = type.__new__(type, "ephemeral%s" % Formsetclass.__name__, (FormsetCurryClass, Formsetclass,), {}) return Formsetclass class LocationHistInline(DefaultWhoWhenInline, admin.StackedInline): model = LocationHist extra = 1 class ResolutionHistInline(DefaultWhoWhenInline, admin.StackedInline): radio_fields = { 'polarity': admin.VERTICAL } model = ResolutionHist extra = 1 class InjuryAdmin(admin.ModelAdmin): list_display = ('discover_when', 'model_name', 'enter_when', 'enter_who') # can't find where this is doing anything date_heirarchy = 'discover_when' # DateTimeFields don't work. #prepopulated_fields = { 'discover_when': ('enter_when',) } # I didn't go far down this path. # http://code.djangoproject.com/wiki/NewformsHOWTO#Q:HowcanIpassextracontextvariablesintomyaddandchangeviews # but you often don't have to go this far because the instance, at least for the main object albeit not the # formsets, is available at 'original': http://code.djangoproject.com/wiki/NewformsHOWTO#Q:HowdoIgetattheoriginalmodelinatemplate #def change_view(self, request, object_id, extra_context=None): # return super(InjuryAdmin, self).change_view(request, object_id, extra_context={ # 'enter_when': en # }) # the Model has no notion of logged in users, so this has to be overridden in # the admin app's options object where we can access the 'request'. def save_model(self, request, obj, form, change): if not obj.id: # STEP 3 (or, half of it) obj.enter_who = request.user # obj.enter_when updated in models.py because it doesn't need the request object return super(InjuryAdmin, self).save_model(request, obj, form, change) # another approach to cramming users into the inline formsets might be here, but we do # it in the other class. #def save_formset(self, request, form, formset, change): # #if not obj.id: # # obj.enter_who = request.user # return super(InjuryAdmin, self).save_formset(request, form, formset, change) # return a form class (not a form object) that will make forms that always have the logged-in # user prepopulated in enter_who field. We also override the value upon saving the object---we don't take the # value from the form, where a malicious browser could alter it---but this way the user can # als see the value on the page when adding a new item, not just when changing an existing one. def get_form(self, request, obj=None, **kwargs): # the get_form method of ModelAdmin returns a class, not an instance of a class. # if we are making a form for an Add page, then InjuryAdmin's get_form will return # child of the class that would otherwise be returned, one which will cram some default # values into the 'initial' parameter whenever this tweaked class is instantiated. Formclass = super(InjuryAdmin, self).get_form(request, obj, **kwargs) # prepopulate an empty model object, which *we will throw away* emptyinjury = Injury() emptyinjury.enter_who = request.user #emptyinjury.enter_when = datetime.datetime.today() # translate the model's inner data types to suitable strings for ModelForm's 'initial' parameter # see django.forms.models.model_to_dict closure_initial = { 'enter_who': None, # 'enter_when': None } #for (k, v) in ((field.name, field.value_from_object(emptyinjury)) for field in emptyinjury._meta.fields if field.name in closure_initial): # closure_initial[k] = v closure_initial = model_to_dict(emptyinjury, fields=closure_initial) # this will set the initial value at the moment of the form's rendering rather than right now, at the # moment of class definition. no real difference since our ``requests'' are not interactive. closure_initial['enter_when'] = datetime.datetime.today # STEP 1, STEP 2 class CurryClass(DisplayOnlyFormFriend): class_initial = closure_initial # http://web.Ivy.NET/~carton/rant/python-metaclass.txt -- avoid invoking ModelFormMetaclass.__new__ return type.__new__(ModelFormMetaclass, "ephemeral%s" % Formclass.__name__, (CurryClass, Formclass,), {}) inlines = [LocationHistInline, ResolutionHistInline] fieldsets = ( ('Who', { 'fields': ( 'enter_when', 'enter_who', 'discover_when', 'who', ) }), ('What', { 'fields': ( 'model_name', 'sku', 'sku_label', 'manufacturer', 'vendor', 'cogs_when_entered', ) }), ('How', { 'fields': ( 'how', ) }), ) admin.site.register(Injury, InjuryAdmin)