views:

626

answers:

2

I'm looking to do something like this:

http://stackoverflow.com/questions/160009/django-model-limitchoicestouser-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.

+2  A: 

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.

Van Gale
You can also do stuff like this in the admin by overriding the right methods on your ModelAdmin -- most of them get the request as an argument, too.
James Bennett
@James BennettCan you give an example of which method to override in a ModelAdmin? Thanks
Off Rhoden
@James Bennett: Never mind. Found this: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
Off Rhoden
A: 

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!

Antonius Common
however, I realised that it now only works for a new form... and not when editing an object... I must revise.
Antonius Common