Metadata-Version: 2.1
Name: django-admin-client
Version: 0.1.2
Summary: Django admin client
Home-page: https://gitlab.com/y3g0r/django-admin-client
Author: Yegor Ivashchenko
Author-email: yegor.ivashchenko@protonmail.com
License: MIT
Platform: UNKNOWN
Requires-Dist: requests
Requires-Dist: BeautifulSoup4
Requires-Dist: python-dotenv

# Django Admin client

## Installation

```
pip install django-admin-client
```

## Usage

3 main usages:
1. Convenient HTTP GET and POST methods
2. Dynamic Django Admin python client
3. Generating specific Django Admin python client

### 1. Convenient HTTP GET and POST methods

`DjangoAdminBase` will do auto login, session management, and auto csrf token inclusion and auto error detection

```python
from django_admin_client import DjangoAdminBase

basic_client = DjangoAdminBase('http://127.0.0.1:9000/admin', 'superuser', 'why-dont-tell-mom?')

# auto login:
basic_client.get_with_auto_login('/')
# <Response [200]>

# response is just our favorite and well-known response from 'requests' library
type(basic_client.get_with_auto_login('/'))
# requests.models.Response

# auto login (if not yet logged in or session expired),
# csrf token included in POST request automatically,
# errors in response html form detected and provided
basic_client.post_with_csrf_and_auto_login(
    '/auth/user/add/',
    {'username': 'test', 'password1': '123', 'password2': '123'}
)
#{'response': <Response [200]>,
# 'errors': [<div class="form-row errors field-password2">
#  <ul class="errorlist"><li>This password is too short. It must contain at least 8 characters.</li><li>This password is too common.</li><li>This password is entirely numeric.</li></ul>
#  <div>
#  <label class="required" for="id_password2">Password confirmation:</label>
#  <input id="id_password2" name="password2" required="" type="password"/>
#  <div class="help">Enter the same password as before, for verification.</div>
#  </div>
#  </div>]}

basic_client.post_with_csrf_and_auto_login(
    '/auth/user/add/',
    {'username': 'test', 'password1': 'isthislongenough', 'password2': 'isthislongenough'}
)
# {'response': <Response [200]>, 'errors': []}
```

### 2. Dynamic Django Admin python client

`DjangoAdminBase` can also do introspection on Django Admin site and generate specification.

```python
In [10]: basic_client.generate_spec()
Out[10]:
{
  "models": {
    "groups": {
      "id": "groups",
      "app": "auth",
      "name": "group",
      "fields": {
        "name": {
          "name": "name",
          "required": true,
          "default_value": ""
        },
        "permissions": {
          "name": "permissions",
          "required": false,
          "default_value": ""
        }
      }
    },
    "users": {
      "id": "users",
      "app": "auth",
      "name": "user",
      "fields": {
        "username": {
          "name": "username",
          "required": true,
          "default_value": ""
        },
        "password1": {
          "name": "password1",
          "required": true,
          "default_value": ""
        },
        "password2": {
          "name": "password2",
          "required": true,
          "default_value": ""
        }
      }
    }
  }
}
```

This specification can then be fed to `DjangoAdminDynamic` class to get dynamic admin python client.

```python
from django_admin_client import DjangoAdminBase, DjangoAdminDynamic
basic_client = DjangoAdminBase('http://127.0.0.1:9000/admin', 'superuser', 'why-dont-tell-mom?')
spec = basic_client.generate_spec()
dynamic_client = DjangoAdminDynamic.from_spec(spec=spec, client=basic_client)
dynamic_client.users.all()
# {'_response': <Response [200]>, 'ids': ['1', '2']}
```

At the moment you can add objects with `<model-name>.add(item: dict)`:
```python
dynamic_client.users.add({})
# ...
# MissingRequiredArgument: ['username', 'password1', 'password2']

dynamic_client.users.add({'username': 'fromdynamic', 'password1': 'qwertyuio!', 'password2': 'qwertyuio!'})
# {'_response': <Response [200]>, 'id': '3', 'created': True}
```

Get all object ids with `<model-name>.all()`:
```python
dynamic_client.users.all()
# {'_response': <Response [200]>, 'ids': ['1', '2', '3']}
```

Search object id with `<model-name>.find(query: str)`:
```python
dynamic_client.users.find('fromdynamic')
# {'_response': <Response [200]>, 'id': '3'}
```

Get object fields with `<model-name>.get(object_id: str)`:
```python
dynamic_client.users.get('3')
#{'_response': <Response [200]>,
# 'details': {'username': 'fromdynamic',
#  'password': '<N/A>',
#  'first_name': '',
#  'last_name': '',
#  'email': '',
#  'is_active': '',
#  'is_staff': '',
#  'is_superuser': '',
#  'groups': [],
#  'user_permissions': [],
#  'last_login': '',
#  'date_joined': '2019-03-05'}}
```

And delete the object with `<model-name>.delete(object_id: str)`:
```python
dynamic_client.users.delete('3')
# {'_response': <Response [200]>, 'success': True}

dynamic_client.users.all()
# {'_response': <Response [200]>, 'ids': ['1', '2']}
```

### 3. Generating specific Django Admin python client

`DjangoAdminDynamic` is quite useful for quick terminal sessions.

Auto-completion for `DjangoAdminDynamic` clients is provided in python interpreter when hitting `<tab>` because attributes
of a client are introspected at run time with `dir` built-in python funciton.

But writing source code with `DjangoAdminDynamic` is not that pleasant because
there's no way for IDE to know what attributes will object have at run time.

For this purpose `generate-package` command is provided with `django-admin-package`.

Example
```
$ generate-package
Base url (including /admin): http://localhost:9000/admin
Superuser username: superuser
Superuser password:
Site name: fresh project
Path to package (default: /tmp): .
Version (default: 1.0):
Generated package in ./freshproject-admin-client
```

Generated project structure:
```
$ tree freshproject-admin-client
freshproject-admin-client
├── freshproject_admin_client
│   ├── client.py
│   ├── __init__.py
│   └── spec.json
├── README.md
└── setup.py

1 directory, 5 files
```

You can version control generated package, install it, upload to PyPI.

Example usage of a generated client:
```
$ ipython
Python 3.6.7 (default, Nov  9 2018, 21:20:52)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.3.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from freshproject_admin_client import FreshProjectDjangoAdminClient

In [2]: client = FreshProjectDjangoAdminClient('http://localhost:9000/admin', 'superuser', 'why-dont-tell-mom?')

In [3]: client.auth.users.all()
Out[3]: {'_response': <Response [200]>, 'ids': ['4', '1', '2']}

In [4]: client.auth.users.add()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-213c8509773c> in <module>
----> 1 client.auth.users.add()

TypeError: add() missing 3 required positional arguments: 'username', 'password1', and 'password2'

In [5]: client.auth.users.add('from_freshproject_client', '123qweASD)_+', '123qweASD)_+')
Out[5]: {'_response': <Response [200]>, 'id': '5', 'created': True}

In [6]: client.auth.users.get('5')
Out[6]:
{'_response': <Response [200]>,
 'details': {'username': 'from_freshproject_client',
  'password': '<N/A>',
  'first_name': '',
  'last_name': '',
  'email': '',
  'is_active': '',
  'is_staff': '',
  'is_superuser': '',
  'groups': [],
  'user_permissions': [],
  'last_login': '',
  'date_joined': '2019-03-05'}}

In [7]: client.auth.users.find('from_freshproject_client')
Out[7]: {'_response': <Response [200]>, 'id': '5'}

In [8]: client.auth.users.delete('5')
Out[8]: {'_response': <Response [200]>, 'success': True}
```

