Question

I'm looking to do something like this:

Model limit_choices_to={'user': user}

with some differences.

Some models may explain:

class Job(models.Model):
    name = models.CharField(max_length=200)
    operators = models.ManyToManyField(User)

class Activity(models.Model):
    job = models.ForeignKey(Job)
    job_operators = models.ManyToManyField(User, limit_choices_to={user: Job.operators} blank=True, null=True)

Note: the syntax is not intended to necessarily correct, but illustrative.

Now, I've had some success at getting the current user, using middleware, as some answers on SO depict, however, I was hoping that I might get the current Job, via request.POST, such that, if the Activity were saved, I would be able to discern the current Job, and therefore the subset of Users as operators, that it turn, would be the user set to choose from in the Activity model.

In other words, based on the selections of a ManyToManyField in the parent field, offer that sub-selection to the child field, or, if John, Jim, Jordan and Jesse worked on a Job, choose from only those names to describe work on an Activity, within and attributed to that Job.

BTW, here's my naive attempt in middleware:

# threadlocals middleware
try:
    from threading import local
except ImportError:
    from django.utils._threading_local import local

_thread_locals = local()
def get_current_user():
    return getattr(_thread_locals, 'user', None)

def get_current_job():
    return getattr(_thread_locals, 'job', None)

class ThreadLocals(object):
    """Middleware that gets various objects from the
    request object and saves them in thread local storage."""
    def process_request(self, request):
        _thread_locals.user = getattr(request, 'user', None)
        _thread_locals.job = getattr(request.POST["job"], 'job', None)

and the Activity model:

operators = modes.ManyToManyField(User, limit_choices_to=dict(Q(User.objects.filter(job==threadlocals.get_current_job)))

Thank you.

Was it helpful?

Solution

Ok, I hate to throw a monkey wrench in your works, but you seriously don't need to use threadlocals hacks.

The user is in the request object which means there is no need to extract it using middleware. It's passed as an argument to every view.

The trick to limiting your form choices is therefore to dynamically change the queryset used in the form as opposed to doing a limit choices in the model.

So your form looks like this:

# forms.py
from django import forms
from project.app.models import Activity
class ActivityForm(forms.ModelForm):
    class Meta:
        model Activity

and your view will look like this:

# views.py
...
form = ActivityForm(instance=foo)
form.fields['job_operators'].queryset = \
    User.objects.filter(operators__set=bar)
...

This is just a quick sketch, but should give you the general idea.

If you're wondering how to avoid threadlocals in the admin, please read Users and the admin by James Bennett.

Edit: Useful form tricks in Django by Collin Grady also shows an example of dynamically setting the queryset in the form __init__ method, which is cleaner than my example above.

OTHER TIPS

But what about the fields you define with list_filter? They only respect limit_choices_to. So if you want to limit the choices of Django Admin filters you have to use limit_choices_to and some Middleware to filter them based on the current user. Or is there an easier solution?

Monkey Wrenches welcome friend!

I'd also found another way, though it seems less direct:

class ActivityForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ActivityForm, self).__init__(*args, **kwargs)

        if self.initial['job_record']:
            jr = JobRecord.objects.get(pk=self.initial['job_record'])
            self.fields['operators'].queryset = jr.operators

    class Meta:
        model = Activity
        exclude = ('duration',)  

I think your ways is better! Thanks heaps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top