简体   繁体   中英

Alternative to global variables in Django?

I need a way to modify one ModelAdmin member variable from another ModelAdmin. So I though maybe I could use a global variable. However, if there where multiple users using the app at the same time, then the global variable would keep on getting changed unexpectedly and all hell would break loose.

Is there any method in Django would allow me to modify one ModelAdmin member variable from another ModelAdmin?

Or am I making a design mistake? Am I making this harder than it really is or am I missing something? What about using threading and locks? Message passing??? Events?!?!?! HELP


Here's the whole story. My app lets it's users build a PC by choosing compatible CPU, motherboard, memory, and hard drives (in that order). By choosing a cpu, they're limited to motherboards with the CPU's socket. By choosing a motherboard with DDR3 dimms, they're limited to DDR3 memory, and so on.

Also, sense there can be many of the same parts per system (example: memory modules, but they must be identical), I had to create ManyToManyField relationships and specify the intermediary table (with an extra count field) with the through arg. This requires that InlineAdmin models be used to display the fields in the admin page.

To my delight, the raw_id_field variable caused the dropdown widget to be replaced with a button that pops up a form identical to change_list.html and allows users to filter/sort/search for the part they want. However, this was not good enough for my boss. Now I need those filters to be predefined according to earlier selections (ie filter for Memory with DDR3 after selecting a motherboard with DDR3). So I implimented this: Default filter in Django admin but I need a way to set the CpuAdmin.default_filters from the PcAdmin dynamically based on what other choices they made.

My models, only including one part model for brevity:

# models.py
class CPU(Part):
    partNum = models.CharField(max_length=60)
    price = models.DecimalField(precision=2)
    socket = models.CharField(max_length=60)
    numCores = models.CharField(max_length=60)

class PC(models.Model):
    name = models.CharField(max_length=60)
    customer = models.CharField(max_length=60)
    cpuChoices = models.ManyToManyField(CPU, through='PcCpuChoice')
    memoryChoices = models.ManyToManyField(Memory, through='PcMemoryChoice')
    hardDriveChoices = models.ManyToManyField(HardDrive, through='PcHardDriveChoice')
    motherBoardChoices = models.ManyToManyField(MotherBoard, through='PcMotherboardChoice')

class PcCpuChoice(models.Model):
    pc = models.ForeignKey(PC, unique=False)
    cpu = models.ForeignKey(CPU, unique=False)
    count = models.IntegerField()

# admin.py
class PartAdmin(admin.ModelAdmin):
    class Meta:
        abstract = True
    search_fields = ['partNum', 'description', 'model']
    default_filter = []

    def changelist_view(self, request, extra_context=None):
        if not request.GET.has_key(self.default_filter[0]):
            q = request.GET.copy()
            q[self.default_filter[0]] = self.default_filter[1]
            request.GET = q
            request.META['QUERY_STRING'] = request.GET.urlencode()
        return super(PartAdmin,self).changelist_view(request, extra_context=extra_context)

class CpuAdmin(PartAdmin):
    list_filter = ['brand', 'socket', 'numCores', 'graphics']
    list_display = ('partNum', 'description', 'brand', 'model', 'markupPrice', 'clockSpeed', 'watts', 'voltage')
    default_filter = ['numCores','8'] # need to change this from PcAdmin!!!

class PcCpuInline(admin.TabularInline):
    model = PcCpuChoice
    extra = 1
    max_num = 1
    raw_id_fields = ['cpu']

class PcAdmin(admin.ModelAdmin):
    inlines = [PcCpuInline, PcMotherboardInline, PcMemoryInline, PcHardDriveInline]

admin.site.register(PC, PcAdmin)

This is not an answer, but rather a nudge in the right direction.

There are more variable contexts that local and global . In your case the context is user or perhaps build (if the user has multiple builds going simultaneously).

Note that the changelist_view() method takes a request object. From this you can get the user , the session (with an arbitrary amount of stuff hanging off of it), and all other manner of good state information.

One further observation: in a multi-threaded, multi-process web environment, there really is no "global" in the sense you are used to thinking about it. Although it's possible to create a "global" in such an environment (eg using memcached ), you're going to have to work pretty hard at it.

Thank you @PeterRowell for pointing me in the right direction. I used django sessions to store the filters, javascript to send a request to the server to update the filters when they load the form and to delete them when they leave the form, some view functions to handle those request, added a function to the model (with the request as a parameter) to update the filters per what parts where already saved, and override the changelist_view function of the PartAdmin to use the filters in request.session to modify the query string. That was a lot of code in a lot of different files, but here are some of the highlights that will hopefully help someone looking for a solution like this:

All the models posted in the question stayed pretty much the same.

The views for the PC:

def update_filters(request):
    try:
        # get POST data
        url = request.POST['form_url']
        id = url.split('/')[-2:-1][0]
        system_type = url.split('/')[-3:-2][0]

        if id is not "new":
            system_content_type = ContentType.objects.get(app_label="systems", model=system_type.rstrip('s'))
            system = system_content_type.get_object_for_this_type(id=id)
            system.set_filters(request)
        else:
            request.session['filters'] = ''
        return HttpResponse("Filters where updated.")
    except:
        return HttpResponse("Select a part and click 'Save and continue' to set the filters.")


def delete_filters(request):
    request.session['filters'] = ''
    return HttpResponse("Filters where deleted.")

Here is the javascript that was placed in the change_form.html (added it via the extra_context param in PcAdmin add_view and change_view functions)

    function post_to_url(path, params, method) {
        method = method || "post"; // Set method to post by default, if not specified.

        // The rest of this code assumes you are not using a library.
        // It can be made less wordy if you use one.
        var form = document.createElement("form");
        form.setAttribute("method", method);
        form.setAttribute("action", path);

        for(var key in params) {
            if(params.hasOwnProperty(key)) {
                var hiddenField = document.createElement("input");
                hiddenField.setAttribute("type", "hidden");
                hiddenField.setAttribute("name", key);
                hiddenField.setAttribute("value", params[key]);

                form.appendChild(hiddenField);
             }
        }

        var frame = document.createElement("iframe");
        frame.name="hidden-form";
        form.target="hidden-form";
        document.body.appendChild(form);
        document.body.appendChild(frame);
        form.submit();
    }

    // when they load the page, set the filters
    $(document).ready(function(){
        post_to_url("/quotegenerator/systems/update_filters/",{'form_url': document.URL});
    });

    // when they exit, delete the filter cookie
    window.onbeforeunload = function() {
        post_to_url("/quotegenerator/systems/delete_filters/", {});
    }

And finally, the function that was added to PartAdmin:

    def set_filters(self, request):
            # query and get the parts
            try:
                    cpu = self.cpuChoices.get()
            except:
                    cpu = False
            try:
                    mobo = self.motherBoardChoices.get()
            except:
                    mobo = False
            try:
                    mem = self.memoryChoices.get()
            except:
                    mem = False
            try:
                    hdd = self.hardDriveChoices.get()
            except:
                    hdd = False

           # for each combo of parts, figure out whats required
            # no parts at all
            if not (mobo or cpu or mem or hdd):
                    request.session['filters'] = ''
            # mobo only
            elif mobo and not (cpu or mem or hdd):
                    request.session['filters'] = 'socket='+mobo.socket
            # cpu only
            elif cpu and not (mobo or mem or hdd):
                    request.session['filters'] = 'socket='+cpu.socket
            # memory only
            elif mem and not (mobo or cpu or hdd):
                    request.session['filters'] = 'memType='+mem.memType
            # hard drive only
            elif hdd and not (mobo or cpu or mem):
                    request.session['filters'] = 'interface='+hdd.interface
            # mobo and cpu
            elif mobo and cpu and not (mem or hdd):
                    request.session['filters'] = 'memType='+mobo.memType+'&interface='+mobo.interface
            # mobo and memory
            elif mobo and mem and not (cpu or hdd):
                    request.session['filters'] = 'socket='+mobo.socket+'&interface='+mobo.interface
            # mobo and hd
            elif mobo and hdd and not (cpu or mem):
                    request.session['filters'] = 'socket='+mobo.socket+'&memType='+mobo.memType
            # cpu and mem
            elif cpu and mem and not (mobo or hdd):
                    request.session['filters'] = 'socket='+cpu.socket+'&memType='+mem.memType
            # cpu and hd
            elif cpu and hdd and not (mobo or mem):
                    request.session['filters'] = 'socket='+cpu.socket+'&interface='+hdd.interface
            # memory and hd
            elif mem and hdd and not (mobo or cpu):
                    request.session['filters'] = 'memType='+mem.memType+'&interface='+hdd.interface
            # mobo cpu and mem
            elif cpu and mobo and mem and not hdd:
                    request.session['filters'] = 'interface='+mobo.interface
            # mobo cpu and hd
            elif mobo and cpu and hdd and not mem:
                    request.session['filters'] = 'memType='+mobo.memType
            # mobo hd and mem
            elif mobo and mem and hdd and not cpu:
                    request.session['filters'] = 'socket='+mobo.socket
            # cpu mem and hd
            elif cpu and mem and hdd and not mobo:
                    request.session['filters'] = 'socket='+cpu.socket+'&memType='+mem.memType+'&interface='+hdd.interface
            # all parts
            else:
                    request.session['filters'] = ''

Oh, and the changelist_view function of PartAdmin did change a little:

def changelist_view(self, request, extra_context=None):
    if ('filters' in request.session):
        q = request.GET.copy()
        for filter in request.session['filters'].split('&'):
            key, value = urllib.splitvalue(filter)
            # check if the request does not already use the filter
            # and that the model has the attribute to filter for
            if (not request.REQUEST.has_key(key)) and (key in self.list_filter):
                q[key] = value
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()
    return super(PartAdmin,self).changelist_view(request, extra_context=extra_context)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM