How to execute code from admin page in django
Tags: python, django
Published: 2020-05-23 02:02:37

### How to execute code from admin page in django

So I've been searching in stackoverflow a while ago and came across a nice question, The owner wants to have a button on the admin page to execute code that's _dynamically_ generated. I've answered it there but I still want to emphasize how important this topic is and this is why I'm writing this right now. 

This is what the `execute code` button looks like

![Execute button image](https://i.imgur.com/OnhggVt.png?1)

__WE WILL NOT BE CREATING THE SAME THING FROM THE QUESTION, WE'LL BE CREATING SOMETHING MORE GENERIC THAT'S NOT QUESTION RELATED__
<hr>
This article will be divided into sections as follows 

1. [Naive implementation, Without taking anything in consideration.](#naive-implementation)
2. [Execute the code with `Execute` button on admin page.](#execute-button)
3. [Security measures, What's the worst that can happen?](#security-measures)
4. [Best practices of _dynamically_ generated code.](#best-practices)

Let's get started.
<hr>

<h3 id="naive-implementation">Naive implementation</h3>

First of all, We're gonna have a model called `Rule` and this `Rule` model will have attributes 2 attributes, an attribute to hold the code itself and another for tracking if this was executed before.

So basically a `Rule` is an executable object, It's just a fancy name, name it whatever you want.

This is how I've written the code
```
# models.py
class  Rule(models.Model):
    code = models.TextField()
    was_executed_before = models.BooleanField(default=False) # it was not executed before.
 ```

Pretty much everything is self-explanatory, We want to do something at the row level, to the `Rule` itself, this means we're gonna add a method to the `Rule` model here, We'll call it `execute` and when we call `rule_obj.execute()`, This executes the code. Python has a builtin function `eval(code: str)` that takes the code as a string and evaluates it, returns what the code have returned.

__e.g__

```
# ran in IDLE
x = eval('[1, 2, 3]')
x 
# [1, 2, 3]
type(x)
# <class 'list'>
```
This is `eval()` in a nutshell, back on topic, This is our model now

```
# models.py
class  Rule(models.Model):
    code = models.TextField()
    was_executed_before = models.BooleanField(default=False) # it was not executed before.
    
    def execute(self):
        # omit return or leave it depending on your needs
        return eval(self.code)
 ```

Let's talk a bit about this, Are we passing a `TextField` object to eval? actually, if you're using `PyCharm` IDE, the `self.code` will be marked yellow, You can't pass a non-string in `eval()`, This actually takes us to how `repr()` and `str()`work, the `TextField` object is represented by it's value this is why this works and this is 100% correct, It's not a hack, It's the same as `Rule.code = "Whatever"`, It's a string.

Let's register our model in the admin page first

```
# admin.py
....
from .models import Rule

admin.site.register(Rule)  
```

Let's create a new `Rule` object, btw you could have used `python manage.py shell` to do this and this is totally fine but we need to add a button in the admin page anyway.

This is the `Rule` object I've created, It's useless, Let's be sure of our logic, We'll override the `save` method temporary to execute it after it gets saved.

![Creating a Rule object from admin panel](https://i.imgur.com/GUTitib.png)

add this to your `Rule` model
```
def save(self, **kwargs):
    super().save(**kwargs)
    self.execute()
```
Now, Open the `Rule` object you've created and click save without changing anything, This calls `save` and you'll notice in the console you've `Article written by LeOndaz` printed. Our naive implementation works.
<hr>
<h3 id="execute-button">Adding an execution button</h3>
Let's add a button to execute this rule when we click this button, To add a button, We'll override how django renders the `change_form`, basically, the `change_form` is the form in the images I used above, the form used in changing models data.<br>

Overriding django templates is straight-forward, The path to override this template for a specific model is `<app_name>/templates/admin/<app_name>/<model_name>/change_form.html` <br>
In my case, My app is called `core` and my model is called `Rule` so I'll use `core/templates/admin/core/rule/change_form.html` 

This is the content I've.

```
{# this is core/templates/admin/core/rule/change_form.html #}
{% extends 'admin/change_form.html' %}

{% block submit_buttons_bottom %}
    {{ block.super }} {# This calls super to get the default layout#}
    {# now let's add a button underneath it  #}
    <div  class="submit-row">  {# this button will POST request with the name 'execute' #}
       <input  type="submit"  value="Execute the rule"  name="execute">
    </div>
{% endblock %}
```
__NOTES__: `submit-row` class is a django builtin that they use in all of their buttons, This creates the grey area that contains the buttons like this. 

![Grey box image](https://i.imgur.com/pnPsReX.png)

`value` is the text that will be shown on the button.  We didn't include `<form>` because we're actually inside a form.

![Source code image to prove that we're really in a form](https://i.imgur.com/VWIg2H3.png)


Notice the area surrounding the button, this is `submit-row` in action.

![Grey area around the button](https://i.imgur.com/g2XAm7k.png)

Let's add the functionality of this button using a `ModelAdmin`
```
# admin.py
from django.shortcuts import redirect

class RuleAdmin(admin.ModelAdmin):
    def response_change(self, request, obj):
        if "execute" in request.POST:
            if not obj.was_executed_before:
                try:    
                    obj.execute()
                    obj.was_executed_before = True
                    obj.save()
	            except (ValueError, TypeError):
                    pass
        return redirect(".")
        # if we didn't find 'execute' in POST data
        return  super().response_change(request, obj)

# don't forget to register the RuleAdmin with Rule
admin.site.register(Rule, RuleAdmin)
```

Let's save, go to our button and click it.
```
[22/May/2020 23:26:01] "GET /admin/core/rule/1/change/ HTTP/1.1" 200 4751
[22/May/2020 23:26:02] "GET /admin/jsi18n/ HTTP/1.1" 200 3223
Article written by LeOndaz
[22/May/2020 23:26:04] "POST /admin/core/rule/1/change/ HTTP/1.1" 302 0
[22/May/2020 23:26:04] "GET /admin/core/rule/1/change/ HTTP/1.1" 200 4759
```
So it is actually working.
<hr>
<h3 id="security-measures">Security measures</h3>
I've added those to the top of my `models.py` and pretty much any basic `models.py` file will have those

```
from django.contrib.auth import get_user_model
User = get_user_model()
```
Now imagine owning a blog with moderators, One of those moderators can use Python, So he created this rule in the

![Imgur](https://i.imgur.com/z5IsLf0.png)

and he got this output `['test']`
So he actually can access everything on your server, He can get all of your users and change their password to something that he knows and bam, Your server is hacked! Now imagine if he's not a moderator, Someone who got access to the admin page, This is serious, This is why the implementation mentioned is only for demonstration purposes to make sure you get the idea.
<hr>
<h3 id="best-practices">Best practices of dynamically generated code</h3>

One of the best practices of creating dynamically generated code is to verify that this code matches a specific pattern, You don't want to prevent typing `eval()` in the code, You want to allow typing something and prevent all others, This is why we'll use regex, We want to prevent running code that we don't like, including `eval()`.

Say we want to allow `print` and prevent `eval`, we won't verify if `eval()` is in the code, instead, we will verify if `print()` is there using regex and if `eval()` is found, It's automatically ignored.

Matching print statements, tested on [regexr](https://regexr.com/)

```
print\("[a-zA-Z0-9]"\)
```

So this matches any `print("whatever")` and it automatically ignores any other code, So instead of limiting the code to use, We limit it to the code we want to use, I hope this makes sense.

Now, in any file (but you'll have to import it) or in `models.py`, add this function
```
# models.py
import re
valid_code_pattern = """print\("[a-zA-Z0-9]"\)"""
def valid_python_code(code):
    if re.match(valid_code_pattern, code):
        return True
    return False
```

This will return `True` if our code is valid with respect to how we defined the word _Valid_.
```
def  execute(self):
    if valid_python_code(self.code):
        return eval(self.code)
    else:
        raise Exception
```

and this should only allow `print` statements, This is much better and _SAFER_.

## Final Thoughts
[Here's](https://stackoverflow.com/questions/61842009/how-do-i-make-the-user-edit-update-the-py-file-in-django) the question I was talking about

So hopefully you've understood everything presented in this article, Feel free to say anything that can improve future articles but till then, stay safe.