0

I'm creating an app. that will generate math problems. They're specific problems where some parameters can be altered. Each problem will be different, and require a different method to solve (all of which will be programatically implemented).

For example: models.py

import random
from django.db import models

class Problem(models.Model):
    unformattedText = models.TextField()

    def __init__(self, unformattedText, genFunction, *args, **kwargs):
        super(Problem, self).__init__(*args, **kwargs)
        self.unformatedText = unformattedText
        self.genFunction = genFunction

    def genQAPair():
        self.genFunction(self.unformattedText)

views.py

def genP1(text):
    num_1 = random.randrange(0, 100)
    num_2 = random.randrange(0, 100)
    text.format((num_1, num_2))
    return {'question':text, 'answer':num_1 - num_2}

def genP2(text, lim=4):
    num_1 = random.randrange(0, lim)
    text.format(num_1)
    return {'question':text, 'answer':num_1*40}


p1 = Problem(
        unformattedText='Sally has {} apples. Frank takes {}. How many apples does Sally have?',
        genFunction=genP1
)
p1.save()

p2 = Problem(
        unformattedText='John jumps {} feet into the air. How long does it take for him to age?',
        genFunction=genP2
)
p2.save()

When I try this, the function isn't actually saved. Django just saves the integer 1. When I initiate an instance of the model, the function is there as intended, but apparently only 1 is saved to the database.

Bonus question: I'm actually beginning to question whether or not I even need Django models for this. I'm using Django because it's super easy to get everything onto a webpage. Is there a better way to do this? (Maybe store the text of each problem in a JSON file and the generating functions in some separate script.)

AmagicalFishy
  • 1,249
  • 1
  • 12
  • 36

2 Answers2

0

There are a couple of options, depending on the actual task. I ranged them starting with the most safe option to the most dangerous (but flexible):

1. Store function identifiers:

You can store genP1 and genP2 as 'genP1' and 'genP2' - i.e. by name (or you can use any other unique identifier).

Pros:

  1. You can validate user input and execute trusted code only, because in this case you control almost everything.
  2. You can easily debug your functions, because they are part of your system.

Cons:

  1. You need to define all your functions in the code. That means if you want to add new function, you need to redeploy your application.
  2. If you are storing function names, you need to manually import module (or package) with the functions and call them.
  3. If you are storing identifiers, you need to define a mapping {identifier: path to actual function}.

2. Use DSL

You can write your own DSL (or use existing)

Pros:

  1. You can add new functions at runtime without redeploying application.
  2. You can control which code can user execute.
  3. You can see source code for your functions.

Cons:

  1. It is hard to write safe and flexible DSL, especially if you want to call some python code from it.
  2. It is hard to debug huge functions.

3. Serialize them

You can serialize functions using pickle

Pros:

  1. You can add new functions at runtime without redeploying application.
  2. Easier than writing own DSL.

Cons:

  1. Unsafe - you must not execute untrusted code. If you allow users to create their own functions, serialization is not your way - define (or use existing) safe DSL instead.
  2. It might be impossible to show source python code for the serialized function. For more information: How can I get the source code of a Python function?
  3. It is hard to debug huge functions.

4. Just store actual Python code

Just store the python source code in the DB as a string.

Pros:

  1. You can add new functions at runtime without redeploying application.
  2. You can see source code without any additional processing.
  3. Easier than writing own DSL.

Cons:

  1. Unsafe - you must not execute untrusted code. If you allow users to create their own functions, storing source code is not your way - define (or use existing) safe DSL instead.
  2. It is hard to debug huge functions.
awesoon
  • 32,469
  • 11
  • 74
  • 99
0

The persistence layer for a Django application is the database, and the database schema is specified by your model definitions. In this case you've only defined a single field in your model, unformattedText; you haven't specified any storage for the corresponding function. Your self.genFunction = genFunction is just creating an attribute on an object in memory; it won't be persisted.

There are various possible ways to store the function. You could store it as raw text; you could store it as a pickle blob; you could store the function path and name (e.g. "my.path.to.problems.genP1"); or do something else. In any case, you'll need to create a database field for that information.

Here is a rough outline of an example solution using the function path:

models.py

class Problem(models.Model):
    unformattedText = models.TextField()
    genPath = models.TextField()

views.py

import importlib

def problem_view(request, problem_id):
    problem = Problem.objects.get(id=problem_id)
    gen_path, gen_name = problem.genPath.rsplit(".", 1)
    gen_module = importlib.import_module(gen_path)
    gen_function = getattr(gen_module, gen_name)

    context = gen_function(problem.unformattedText)
    return render(request, 'template.html', context)

Only you can determine if you need to use a database at all. If you only have a few fixed questions then you could just stuff everything into a Python file and be done with it. But there are advantages to using Django's models, including the ability to use the admin.

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102