Merge pull request #12 from ribaguifi/dev/api-writable
Update some API endpoints to make it writable
This commit is contained in:
commit
5e6cd2f147
|
@ -1,5 +0,0 @@
|
||||||
SECRET_KEY=k_=*vfue(^campsl63)7w5m&cu9u4o4-!vaw94qzyrymyv0hgg
|
|
||||||
DEBUG=True
|
|
||||||
ALLOWED_HOSTS=.localhost,127.0.0.1
|
|
||||||
DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME
|
|
||||||
STATIC_ROOT=PATH_TO_STATIC_ROOT
|
|
|
@ -1,70 +0,0 @@
|
||||||
We need have python3.6
|
|
||||||
|
|
||||||
#Install Packages
|
|
||||||
```bash
|
|
||||||
apt=(
|
|
||||||
bind9utils
|
|
||||||
ca-certificates
|
|
||||||
gettext
|
|
||||||
libcrack2-dev
|
|
||||||
libxml2-dev
|
|
||||||
libxslt1-dev
|
|
||||||
ssh-client
|
|
||||||
wget
|
|
||||||
xvfb
|
|
||||||
zlib1g-dev
|
|
||||||
git
|
|
||||||
iceweasel
|
|
||||||
dnsutils
|
|
||||||
postgresql-contrib
|
|
||||||
)
|
|
||||||
sudo apt-get install --no-install-recommends -y ${apt[@]}
|
|
||||||
```
|
|
||||||
|
|
||||||
It is necessary install *wkhtmltopdf*
|
|
||||||
You can install it from https://wkhtmltopdf.org/downloads.html
|
|
||||||
|
|
||||||
Clone this repository
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/ribaguifi/django-orchestra
|
|
||||||
```
|
|
||||||
|
|
||||||
Prepare env and install requirements
|
|
||||||
```bash
|
|
||||||
cd django-orchestra
|
|
||||||
python3.6 -m venv env
|
|
||||||
source env/bin/activate
|
|
||||||
pip3 install --upgrade pip
|
|
||||||
pip3 install -r total_requirements.txt
|
|
||||||
pip3 install -e .
|
|
||||||
```
|
|
||||||
|
|
||||||
Configure project using environment file (you can use provided example as quickstart):
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
Prepare your Postgres database (create database, user and grant permissions):
|
|
||||||
```sql
|
|
||||||
CREATE DATABASE myproject;
|
|
||||||
CREATE USER myuser WITH PASSWORD 'password';
|
|
||||||
GRANT ALL PRIVILEGES ON DATABASE myproject TO myuser;
|
|
||||||
```
|
|
||||||
|
|
||||||
Prepare a new project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
django-admin.py startproject PROJECT_NAME --template="orchestra/conf/ribaguifi_template"
|
|
||||||
```
|
|
||||||
|
|
||||||
Run migrations:
|
|
||||||
```bash
|
|
||||||
python3 manage.py migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
(Optional) You can start a Django development server to check that everything is ok.
|
|
||||||
```bash
|
|
||||||
python3 manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your browser.
|
|
|
@ -93,7 +93,7 @@ Remember create a database for your project and give permitions for the correct
|
||||||
```
|
```
|
||||||
psql -U postgres
|
psql -U postgres
|
||||||
psql (12.4)
|
psql (12.4)
|
||||||
Digite «help».
|
Digite «help» para obtener ayuda.
|
||||||
|
|
||||||
postgres=# CREATE database orchesta;
|
postgres=# CREATE database orchesta;
|
||||||
postgres=# CREATE USER orchesta WITH PASSWORD 'orquesta';
|
postgres=# CREATE USER orchesta WITH PASSWORD 'orquesta';
|
||||||
|
|
|
@ -17,7 +17,7 @@ class SetPasswordSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
""" support for postonly_fields, fields whose value can only be set on post """
|
""" support for postonly_fields, fields whose value can only be set on post """
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
""" calls model.clean() """
|
""" calls model.clean() """
|
||||||
attrs = super(HyperlinkedModelSerializer, self).validate(attrs)
|
attrs = super(HyperlinkedModelSerializer, self).validate(attrs)
|
||||||
|
@ -39,7 +39,7 @@ class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
instance = ModelClass(**validated_data)
|
instance = ModelClass(**validated_data)
|
||||||
instance.clean()
|
instance.clean()
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def post_only_cleanning(self, instance, validated_data):
|
def post_only_cleanning(self, instance, validated_data):
|
||||||
""" removes postonly_fields from attrs """
|
""" removes postonly_fields from attrs """
|
||||||
model_attrs = dict(**validated_data)
|
model_attrs = dict(**validated_data)
|
||||||
|
@ -49,12 +49,12 @@ class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
if attr in post_only_fields:
|
if attr in post_only_fields:
|
||||||
model_attrs.pop(attr)
|
model_attrs.pop(attr)
|
||||||
return model_attrs
|
return model_attrs
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
""" removes postonly_fields from attrs when not posting """
|
""" removes postonly_fields from attrs when not posting """
|
||||||
model_attrs = self.post_only_cleanning(instance, validated_data)
|
model_attrs = self.post_only_cleanning(instance, validated_data)
|
||||||
return super(HyperlinkedModelSerializer, self).update(instance, model_attrs)
|
return super(HyperlinkedModelSerializer, self).update(instance, model_attrs)
|
||||||
|
|
||||||
def partial_update(self, instance, validated_data):
|
def partial_update(self, instance, validated_data):
|
||||||
""" removes postonly_fields from attrs when not posting """
|
""" removes postonly_fields from attrs when not posting """
|
||||||
model_attrs = self.post_only_cleanning(instance, validated_data)
|
model_attrs = self.post_only_cleanning(instance, validated_data)
|
||||||
|
@ -64,7 +64,10 @@ class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class RelatedHyperlinkedModelSerializer(HyperlinkedModelSerializer):
|
class RelatedHyperlinkedModelSerializer(HyperlinkedModelSerializer):
|
||||||
""" returns object on to_internal_value based on URL """
|
""" returns object on to_internal_value based on URL """
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
url = data.get('url')
|
try:
|
||||||
|
url = data.get('url')
|
||||||
|
except AttributeError:
|
||||||
|
url = None
|
||||||
if not url:
|
if not url:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'url': "URL is required."
|
'url': "URL is required."
|
||||||
|
@ -80,16 +83,16 @@ class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||||
validators=[validate_password], write_only=True, required=False,
|
validators=[validate_password], write_only=True, required=False,
|
||||||
style={'widget': widgets.PasswordInput})
|
style={'widget': widgets.PasswordInput})
|
||||||
|
|
||||||
def validate_password(self, attrs, source):
|
def validate_password(self, value):
|
||||||
""" POST only password """
|
""" POST only password """
|
||||||
if self.instance:
|
if self.instance:
|
||||||
if 'password' in attrs:
|
if value:
|
||||||
raise serializers.ValidationError(_("Can not set password"))
|
raise serializers.ValidationError(_("Can not set password"))
|
||||||
elif 'password' not in attrs:
|
elif not value:
|
||||||
raise serializers.ValidationError(_("Password required"))
|
raise serializers.ValidationError(_("Password required"))
|
||||||
return attrs
|
return value
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
""" remove password in case is not a real model field """
|
""" remove password in case is not a real model field """
|
||||||
try:
|
try:
|
||||||
|
@ -102,7 +105,7 @@ class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
|
||||||
if password is not None:
|
if password is not None:
|
||||||
attrs['password'] = password
|
attrs['password'] = password
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
password = validated_data.pop('password')
|
password = validated_data.pop('password')
|
||||||
instance = self.Meta.model(**validated_data)
|
instance = self.Meta.model(**validated_data)
|
||||||
|
|
|
@ -66,6 +66,7 @@ INSTALLED_APPS = [
|
||||||
'admin_tools.dashboard',
|
'admin_tools.dashboard',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
|
'django_filters',
|
||||||
'passlib.ext.django',
|
'passlib.ext.django',
|
||||||
'django_countries',
|
'django_countries',
|
||||||
# 'debug_toolbar',
|
# 'debug_toolbar',
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if sys.version_info < (3, 3):
|
|
||||||
cmd = ' '.join(sys.argv)
|
|
||||||
sys.stderr.write("Sorry, Orchestra requires at least Python 3.3, try with:\n$ python3 %s\n" % cmd)
|
|
||||||
sys.exit(1)
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
execute_from_command_line(sys.argv)
|
|
|
@ -1,257 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for {{ project_name }} project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django {{ django_version }}.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/{{ docs_version }}/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from decouple import config, Csv
|
|
||||||
from dj_database_url import parse as db_url
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = '{{ secret_key }}'
|
|
||||||
# SECRET_KEY = config('SECRET_KEY')
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = config('DEBUG', default=False, cast=bool)
|
|
||||||
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
# django-orchestra apps
|
|
||||||
'orchestra',
|
|
||||||
'orchestra.contrib.accounts',
|
|
||||||
'orchestra.contrib.systemusers',
|
|
||||||
'orchestra.contrib.contacts',
|
|
||||||
'orchestra.contrib.orchestration',
|
|
||||||
'orchestra.contrib.bills',
|
|
||||||
'orchestra.contrib.payments',
|
|
||||||
'orchestra.contrib.tasks',
|
|
||||||
'orchestra.contrib.mailer',
|
|
||||||
'orchestra.contrib.history',
|
|
||||||
'orchestra.contrib.issues',
|
|
||||||
'orchestra.contrib.services',
|
|
||||||
'orchestra.contrib.plans',
|
|
||||||
'orchestra.contrib.orders',
|
|
||||||
'orchestra.contrib.domains',
|
|
||||||
'orchestra.contrib.mailboxes',
|
|
||||||
'orchestra.contrib.lists',
|
|
||||||
'orchestra.contrib.webapps',
|
|
||||||
'orchestra.contrib.websites',
|
|
||||||
'orchestra.contrib.letsencrypt',
|
|
||||||
'orchestra.contrib.databases',
|
|
||||||
'orchestra.contrib.vps',
|
|
||||||
'orchestra.contrib.saas',
|
|
||||||
'orchestra.contrib.miscellaneous',
|
|
||||||
|
|
||||||
# Third-party apps
|
|
||||||
'django_extensions',
|
|
||||||
'djcelery',
|
|
||||||
'fluent_dashboard',
|
|
||||||
'admin_tools',
|
|
||||||
'admin_tools.theming',
|
|
||||||
'admin_tools.menu',
|
|
||||||
'admin_tools.dashboard',
|
|
||||||
'rest_framework',
|
|
||||||
'rest_framework.authtoken',
|
|
||||||
'passlib.ext.django',
|
|
||||||
'django_countries',
|
|
||||||
# 'debug_toolbar',
|
|
||||||
|
|
||||||
# Django.contrib
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'django.contrib.admin.apps.SimpleAdminConfig',
|
|
||||||
|
|
||||||
# Last to load
|
|
||||||
'orchestra.contrib.resources',
|
|
||||||
'orchestra.contrib.settings',
|
|
||||||
# 'django_nose',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
ROOT_URLCONF = '{{ project_name }}.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
'orchestra.core.context_processors.site',
|
|
||||||
],
|
|
||||||
'loaders': [
|
|
||||||
'admin_tools.template_loaders.Loader',
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': config(
|
|
||||||
'DATABASE_URL',
|
|
||||||
default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3'),
|
|
||||||
cast=db_url
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
TIME_ZONE = open('/etc/timezone', 'r').read().strip()
|
|
||||||
except IOError:
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
||||||
|
|
||||||
|
|
||||||
# Absolute path to the directory static files should be collected to.
|
|
||||||
# Don't put anything in this directory yourself; store your static files
|
|
||||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
|
||||||
# Example: "/home/media/media.lawrence.com/static/"
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
|
||||||
|
|
||||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
||||||
|
|
||||||
|
|
||||||
# Path used for database translations files
|
|
||||||
LOCALE_PATHS = (
|
|
||||||
os.path.join(BASE_DIR, 'locale'),
|
|
||||||
)
|
|
||||||
|
|
||||||
ORCHESTRA_SITE_NAME = '{{ project_name }}'
|
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
|
||||||
# 'django.middleware.locale.LocaleMiddleware'
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'orchestra.core.caches.RequestCacheMiddleware',
|
|
||||||
# also handles transations, ATOMIC_REQUESTS does not wrap middlewares
|
|
||||||
'orchestra.contrib.orchestration.middlewares.OperationsMiddleware',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'accounts.Account'
|
|
||||||
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
|
||||||
'orchestra.permissions.auth.OrchestraPermissionBackend',
|
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
EMAIL_BACKEND = 'orchestra.contrib.mailer.backends.EmailBackend'
|
|
||||||
|
|
||||||
|
|
||||||
# Needed for Bulk operations
|
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = None
|
|
||||||
|
|
||||||
|
|
||||||
#################################
|
|
||||||
## 3RD PARTY APPS CONIGURATION ##
|
|
||||||
#################################
|
|
||||||
|
|
||||||
# Admin Tools
|
|
||||||
ADMIN_TOOLS_MENU = 'orchestra.admin.menu.OrchestraMenu'
|
|
||||||
|
|
||||||
# Fluent dashboard
|
|
||||||
ADMIN_TOOLS_INDEX_DASHBOARD = 'orchestra.admin.dashboard.OrchestraIndexDashboard'
|
|
||||||
FLUENT_DASHBOARD_ICON_THEME = '../orchestra/icons'
|
|
||||||
|
|
||||||
|
|
||||||
# Django-celery
|
|
||||||
import djcelery
|
|
||||||
djcelery.setup_loader()
|
|
||||||
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
|
|
||||||
|
|
||||||
|
|
||||||
# rest_framework
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
|
||||||
'orchestra.permissions.api.OrchestraPermissionBackend',
|
|
||||||
),
|
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
|
||||||
),
|
|
||||||
'DEFAULT_FILTER_BACKENDS': (
|
|
||||||
('django_filters.rest_framework.DjangoFilterBackend',)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Use a UNIX compatible hash
|
|
||||||
PASSLIB_CONFIG = (
|
|
||||||
"[passlib]\n"
|
|
||||||
"schemes = sha512_crypt, django_pbkdf2_sha256, django_pbkdf2_sha1, "
|
|
||||||
" django_bcrypt, django_bcrypt_sha256, django_salted_sha1, des_crypt, "
|
|
||||||
" django_salted_md5, django_des_crypt, hex_md5, bcrypt, phpass\n"
|
|
||||||
"default = sha512_crypt\n"
|
|
||||||
"deprecated = django_pbkdf2_sha1, django_salted_sha1, django_salted_md5, "
|
|
||||||
" django_des_crypt, des_crypt, hex_md5\n"
|
|
||||||
"all__vary_rounds = 0.05\n"
|
|
||||||
"django_pbkdf2_sha256__min_rounds = 10000\n"
|
|
||||||
"sha512_crypt__min_rounds = 80000\n"
|
|
||||||
"staff__django_pbkdf2_sha256__default_rounds = 12500\n"
|
|
||||||
"staff__sha512_crypt__default_rounds = 100000\n"
|
|
||||||
"superuser__django_pbkdf2_sha256__default_rounds = 15000\n"
|
|
||||||
"superuser__sha512_crypt__default_rounds = 120000\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SHELL_PLUS_PRE_IMPORTS = (
|
|
||||||
('orchestra.contrib.orchestration.managers', ('orchestrate',)),
|
|
||||||
)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.conf.urls import include, url
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'', include('orchestra.urls')),
|
|
||||||
]
|
|
|
@ -1,14 +0,0 @@
|
||||||
"""
|
|
||||||
WSGI config for {{ project_name }} project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
application = get_wsgi_application()
|
|
|
@ -252,7 +252,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
def display_mailboxes(self, address):
|
def display_mailboxes(self, address):
|
||||||
boxes = address.mailboxes.all()
|
boxes = address.mailboxes.all()
|
||||||
return format_html_join(
|
return format_html_join(
|
||||||
'<br>', '<a href="{}">{}</a>',
|
mark_safe('<br>'), '<a href="{}">{}</a>',
|
||||||
[(change_url(mailbox), mailbox.name) for mailbox in boxes]
|
[(change_url(mailbox), mailbox.name) for mailbox in boxes]
|
||||||
)
|
)
|
||||||
display_mailboxes.short_description = _("Mailboxes")
|
display_mailboxes.short_description = _("Mailboxes")
|
||||||
|
@ -261,7 +261,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
def display_all_mailboxes(self, address):
|
def display_all_mailboxes(self, address):
|
||||||
boxes = address.get_mailboxes()
|
boxes = address.get_mailboxes()
|
||||||
return format_html_join(
|
return format_html_join(
|
||||||
'<br>', '<a href="{}">{}</a>',
|
mark_safe('<br>'), '<a href="{}">{}</a>',
|
||||||
[(change_url(mailbox), mailbox.name) for mailbox in boxes]
|
[(change_url(mailbox), mailbox.name) for mailbox in boxes]
|
||||||
)
|
)
|
||||||
display_all_mailboxes.short_description = _("Mailboxes links")
|
display_all_mailboxes.short_description = _("Mailboxes links")
|
||||||
|
|
|
@ -4,7 +4,7 @@ from orchestra.api import router, SetPasswordApiMixin, LogApiMixin
|
||||||
from orchestra.contrib.accounts.api import AccountApiMixin
|
from orchestra.contrib.accounts.api import AccountApiMixin
|
||||||
|
|
||||||
from .models import Address, Mailbox
|
from .models import Address, Mailbox
|
||||||
from .serializers import AddressSerializer, MailboxSerializer
|
from .serializers import AddressSerializer, MailboxSerializer, MailboxWritableSerializer
|
||||||
|
|
||||||
|
|
||||||
class AddressViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class AddressViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
|
@ -17,6 +17,12 @@ class MailboxViewSet(LogApiMixin, SetPasswordApiMixin, AccountApiMixin, viewsets
|
||||||
queryset = Mailbox.objects.prefetch_related('addresses__domain').all()
|
queryset = Mailbox.objects.prefetch_related('addresses__domain').all()
|
||||||
serializer_class = MailboxSerializer
|
serializer_class = MailboxSerializer
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
return self.serializer_class
|
||||||
|
|
||||||
|
return MailboxWritableSerializer
|
||||||
|
|
||||||
|
|
||||||
router.register(r'mailboxes', MailboxViewSet)
|
router.register(r'mailboxes', MailboxViewSet)
|
||||||
router.register(r'addresses', AddressViewSet)
|
router.register(r'addresses', AddressViewSet)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.db import transaction
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer, RelatedHyperlinkedModelSerializer
|
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer, RelatedHyperlinkedModelSerializer
|
||||||
|
@ -8,7 +9,7 @@ from .models import Mailbox, Address
|
||||||
|
|
||||||
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Address.domain.field.model
|
model = Address.domain.field.related_model
|
||||||
fields = ('url', 'id', 'name')
|
fields = ('url', 'id', 'name')
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,6 +36,41 @@ class MailboxSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer
|
||||||
postonly_fields = ('name', 'password')
|
postonly_fields = ('name', 'password')
|
||||||
|
|
||||||
|
|
||||||
|
class AddressRelatedField(serializers.HyperlinkedRelatedField):
|
||||||
|
# Filter addresses by account (user)
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset()
|
||||||
|
return qs.filter(account=self.context['account'])
|
||||||
|
|
||||||
|
|
||||||
|
class MailboxWritableSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
|
addresses = AddressRelatedField(many=True, view_name='address-detail', queryset=Address.objects.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Mailbox
|
||||||
|
fields = (
|
||||||
|
'url', 'id', 'name', 'password', 'filtering', 'custom_filtering', 'addresses', 'is_active'
|
||||||
|
)
|
||||||
|
postonly_fields = ('name', 'password')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['addresses'].context['account'] = self.account
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create(self, validated_data):
|
||||||
|
addresses = validated_data.pop('addresses', [])
|
||||||
|
instance = super().create(validated_data)
|
||||||
|
instance.addresses.set(addresses)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
addresses = validated_data.pop('addresses', [])
|
||||||
|
instance.addresses.set(addresses)
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class RelatedMailboxSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
class RelatedMailboxSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Mailbox
|
model = Mailbox
|
||||||
|
@ -43,7 +79,7 @@ class RelatedMailboxSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSe
|
||||||
|
|
||||||
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
domain = RelatedDomainSerializer()
|
domain = RelatedDomainSerializer()
|
||||||
mailboxes = RelatedMailboxSerializer(many=True, required=False) #allow_add_remove=True
|
mailboxes = RelatedMailboxSerializer(many=True, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Address
|
model = Address
|
||||||
|
@ -51,6 +87,21 @@ class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs = super(AddressSerializer, self).validate(attrs)
|
attrs = super(AddressSerializer, self).validate(attrs)
|
||||||
if not attrs['mailboxes'] and not attrs['forward']:
|
mailboxes = attrs.get('mailboxes', [])
|
||||||
|
forward = attrs.get('forward', '')
|
||||||
|
if not mailboxes and not forward:
|
||||||
raise serializers.ValidationError("A mailbox or forward address should be provided.")
|
raise serializers.ValidationError("A mailbox or forward address should be provided.")
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create(self, validated_data):
|
||||||
|
mailboxes = validated_data.pop('mailboxes', [])
|
||||||
|
obj = super().create(validated_data)
|
||||||
|
obj.mailboxes.set(mailboxes)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
mailboxes = validated_data.pop('mailboxes', [])
|
||||||
|
instance.mailboxes.set(mailboxes)
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
|
@ -10,7 +10,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PaymentSource
|
model = PaymentSource
|
||||||
fields = ('url', 'id', 'method', 'data', 'is_active')
|
fields = ('url', 'id', 'method', 'data', 'is_active')
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
""" validate data according to method """
|
""" validate data according to method """
|
||||||
data = super(PaymentSourceSerializer, self).validate(data)
|
data = super(PaymentSourceSerializer, self).validate(data)
|
||||||
|
@ -20,7 +20,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
raise serializers.ValidationError(serializer.errors)
|
raise serializers.ValidationError(serializer.errors)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def transform_data(self, obj, value):
|
def transform_data(self, obj, value):
|
||||||
if not obj:
|
if not obj:
|
||||||
return {}
|
return {}
|
||||||
|
@ -29,7 +29,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
serializer_class = plugin().get_serializer()
|
serializer_class = plugin().get_serializer()
|
||||||
return serializer_class().to_native(obj.data)
|
return serializer_class().to_native(obj.data)
|
||||||
return obj.data
|
return obj.data
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
def metadata(self):
|
def metadata(self):
|
||||||
meta = super(PaymentSourceSerializer, self).metadata()
|
meta = super(PaymentSourceSerializer, self).metadata()
|
||||||
|
@ -43,3 +43,4 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
class TransactionSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class TransactionSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
|
exclude = ('process',)
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
Django==1.10.5
|
|
||||||
django-fluent-dashboard==0.6.1
|
|
||||||
django-admin-tools==0.8.0
|
|
||||||
django-extensions==1.7.4
|
|
||||||
django-celery==3.1.17
|
|
||||||
celery==3.1.23
|
|
||||||
kombu==3.0.35
|
|
||||||
billiard==3.3.0.23
|
|
||||||
Markdown==2.4
|
|
||||||
djangorestframework==3.4.7
|
|
||||||
ecdsa==0.11
|
|
||||||
Pygments==1.6
|
|
||||||
django-filter==0.15.2
|
|
||||||
jsonfield==0.9.22
|
|
||||||
python-dateutil==2.2
|
|
||||||
django-iban==0.3.0
|
|
||||||
requests
|
|
||||||
phonenumbers
|
|
||||||
django-countries
|
|
||||||
django-localflavor
|
|
||||||
amqp
|
|
||||||
anyjson
|
|
||||||
pytz
|
|
||||||
cracklib
|
|
||||||
lxml==3.3.5
|
|
||||||
selenium
|
|
||||||
xvfbwrapper
|
|
||||||
freezegun==0.3.14
|
|
||||||
coverage
|
|
||||||
flake8
|
|
||||||
django-debug-toolbar==1.3.0
|
|
||||||
django-nose==1.4.4
|
|
||||||
sqlparse
|
|
||||||
pyinotify
|
|
||||||
PyMySQL
|
|
||||||
dj_database_url==0.5.0
|
|
||||||
psycopg2-binary
|
|
||||||
python-decouple
|
|
||||||
https://github.com/glic3rinu/passlib/archive/master.zip
|
|
Loading…
Reference in New Issue