0

I have two different models that I would like to filter similarly by a common field name at different times, so i've written a single context function that handles both models by taking a string as an argument to use as the model name. Right now I'm using eval(), but something in my gut tells me that's a grave error. Is there a more pythonic way to do what I'm describing?

Here's a shortened version of what my code looks like at the moment:

def reference_context(model, value):
    menu = main_menu()
    info = company_info()
    pages = get_list_or_404(eval(model), category = value)

Secondly, is there a way to pass a keyword in a similar fashion, so I could have something along the lines of:

def reference_context(model, category, value):
    menu = main_menu()
    info = company_info()
    pages = get_list_or_404(eval(model), eval(category) = value)

And commentary on any other issue is welcome and greatly encouraged.

skzryzg
  • 1,020
  • 8
  • 25
  • Why do you need to pass the model as a string and not as a reference? E.g. reference_context(MyModel, value) – Ivo van der Wijk Sep 16 '13 at 14:26
  • Not that common as far as I know. In such a case I'd make my own explicit registry of names->models (a simple dict will do) and use that. This way you avoid unexpected security issues when someone injects "User" as model in your forms. – Ivo van der Wijk Sep 16 '13 at 14:47

3 Answers3

1

If they are come from the same module (models.py), you can use getattr to retrieve the model class, and kwargs (a dict with double asterisk) this way:

from myapp import models

def reference_context(model, value):
    menu = main_menu()
    info = company_info()
    pages = get_list_or_404(getattr(models, model), **{category: value})
augustomen
  • 8,977
  • 3
  • 43
  • 63
1

You can use the get_model utility function, which takes the app name and model name.

from django.db.models import get_model
User = get_model("auth", "User") # returns django.contrib.auth.models.User
Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • If you want to use this solution, check this [https://stackoverflow.com/a/4881693/8836745](https://stackoverflow.com/a/4881693/8836745) – Morteza Afshari Nov 24 '21 at 10:22
1

I don't really see why you need to pass the model as a string - just pass the model reference. E.g.

class ModelA(models.Model):
    ...

class ModelB(models.Model):
    ...

def reference_context(model, **kw):
    menu = main_menu()
    info = company_info()
    pages = get_list_or_404(model, **kw)
    # ...

In this setup you can pass any model and any query you want, e.g.

reference_context(ModelA, category="Hello")

or

reference_context(ModelB, item__ordered__lte=now)

As explained in my comment, if you really need to map strings to models, use an explicit registry/mapping. This prevents people from manipulating form data which might allow them to create a User in stead of, for example, a "Book":

model_map = dict(book=ModelA, magazine=ModelB)
reference_context(model_map[model_as_string], ...)
Ivo van der Wijk
  • 16,341
  • 4
  • 43
  • 57