├── .gitignore
├── .travis.yml
├── MANIFEST.in
├── README.md
├── dirtyedit
├── __init__.py
├── admin.py
├── apps.py
├── conf.py
├── forms.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── populate_editor.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── msgs.py
├── templates
│ ├── admin
│ │ └── dirtyedit
│ │ │ ├── change_form.html
│ │ │ ├── change_list.html
│ │ │ └── includes
│ │ │ └── fieldset.html
│ ├── codemirror2
│ │ └── codemirror_script.html
│ └── reversion
│ │ └── change_list.html
├── tests.py
└── utils.py
├── docs
└── img
│ ├── screenshot1.png
│ └── screenshot2.png
├── requirements.txt
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | settings.py
2 | *.sqlite3
3 | .project
4 | .pydevproject
5 | .settings
6 |
7 | __pycache__/
8 | *.py[cod]
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | env/
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 |
59 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - 2.7
5 | - pypy
6 |
7 | env:
8 | - DJANGO=1.10
9 |
10 | install:
11 | - pip install -r requirements.txt
12 |
13 | script:
14 | python setup.py test
15 |
16 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include dirtyedit/migrations *
2 | recursive-include dirtyedit/templates *
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Django Dirty Edit
2 | ==============
3 |
4 | [](https://travis-ci.org/synw/django-dirtyedit)
5 |
6 | A Django application to edit files from the admin interface. This make it possible for example to let graphic
7 | designers edit some css files in the admin interface.
8 |
9 | Install
10 | --------------
11 |
12 | pip install django-dirtyedit
13 |
14 | Add these to INSTALLED_APPS:
15 |
16 | 'dirtyedit',
17 | 'ckeditor',
18 | 'codemirror2',
19 | 'reversion',
20 |
21 | Note: `codemirror2` and `reversion` should be loaded after `dirtyedit`
22 |
23 | Settings
24 | --------------
25 |
26 | Default values are:
27 |
28 | - `DIRTYEDIT_EDIT_MODE = 'code'` : uses codemirror. To use ckeditor set it to `'html'`
29 | - `DIRTYEDIT_CODEMIRROR_KEYMAP = 'default'` : set it to what your like. Ex: `'vim'`, `'emacs'`
30 | - `DIRTYEDIT_AUTHORIZED_PATHS = ('media', 'static', 'templates')` : writing in theses directories and their subdirectories is authorized.
31 | - `DIRTYEDIT_EXCLUDED_PATHS = ()` : to explicitly exclude some paths. Ex: `('media/private')`
32 | - `DIRTYEDIT_CAN_CREATE_FILES = False` : set it to `True` to allow file creation
33 | - `DIRTYEDIT_USE_REVERSION = True` : set it to False to disable reversion
34 |
35 | Management command
36 | ------------------
37 |
38 | A management command is available to populate the database from a directory: example:
39 |
40 | ```
41 | python3 manage.py populate_editor templates
42 | ```
43 |
44 | This will save instances from each of the files that is in the `templates` folder. Note: this is not recursive, only
45 | the files in the directory will be processed
46 |
47 | Warning
48 | --------------
49 |
50 | Handle with care: its pretty easy to break things with this module! Only give access to it to trusted admin users.
51 |
52 | Screenshots
53 | --------------
54 |
55 | Select files:
56 |
57 | 
58 |
59 | Edit files (fullscreen edit is available):
60 |
61 | 
62 |
63 | Todo
64 | --------------
65 |
66 | - Handle file types to auto setup codemirror highlighting mode
--------------------------------------------------------------------------------
/dirtyedit/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.3'
2 | default_app_config = 'dirtyedit.apps.DirtyEditConfig'
--------------------------------------------------------------------------------
/dirtyedit/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.contrib import admin
4 | from django.contrib import messages
5 | from dirtyedit.models import FileToEdit
6 | from dirtyedit.forms import DirtyEditForm
7 | from dirtyedit.utils import read_file, write_file
8 | from dirtyedit.conf import USE_REVERSION
9 |
10 |
11 | admin_class = admin.ModelAdmin
12 | if USE_REVERSION:
13 | from reversion.admin import VersionAdmin
14 | admin_class = VersionAdmin
15 |
16 |
17 | @admin.register(FileToEdit)
18 | class FileToEditAdmin(admin_class):
19 | form = DirtyEditForm
20 | fieldsets = (
21 | (None, {
22 | 'fields': ('content',)
23 | }),
24 | (None, {
25 | 'fields': ('relative_path',)
26 | }),
27 | )
28 |
29 | def save_model(self, request, obj, form, change):
30 | #~ record editor
31 | if getattr(obj, 'editor', None) is None:
32 | obj.editor = request.user
33 | status, msg = write_file(obj.relative_path, obj.content)
34 | if msg != '':
35 | if status == 'warn':
36 | messages.warning(request, msg)
37 | elif status == 'infos':
38 | messages.info(request, msg)
39 | else:
40 | messages.error(request, msg)
41 | return super(FileToEditAdmin, self).save_model(
42 | request, obj, form, change)
43 |
44 | def get_changeform_initial_data(self, request):
45 | if 'fpath' in request.GET.keys():
46 | filepath = request.GET.get('fpath')
47 | status_msg, msg, filecontent = read_file(filepath)
48 | if status_msg is True:
49 | messages.success(request, msg)
50 | return {'content': filecontent, 'relative_path': filepath}
51 | else:
52 | if status_msg == 'warn':
53 | messages.warning(request, msg)
54 | return
55 | elif status_msg == 'infos':
56 | messages.info(request, msg)
57 | return {'relative_path': filepath}
58 | messages.error(request, msg)
59 | return
60 | return
61 |
--------------------------------------------------------------------------------
/dirtyedit/apps.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import ugettext_lazy as _
2 | from django.apps import AppConfig
3 |
4 |
5 | class DirtyEditConfig(AppConfig):
6 | name = "dirtyedit"
7 | verbose_name = _(u"Files editor")
8 |
9 | def ready(self):
10 | pass
11 |
--------------------------------------------------------------------------------
/dirtyedit/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf import settings
4 |
5 |
6 | edit_modes = ('html','code')
7 |
8 | authorized_paths = ('/media', '/static', '/templates')
9 |
10 | EDIT_MODE = getattr(settings, 'DIRTYEDIT_EDIT_MODE', edit_modes[1])
11 | CODEMIRROR_KEYMAP = getattr(settings, 'DIRTYEDIT_CODEMIRROR_KEYMAP', 'default')
12 |
13 | USE_REVERSION = getattr(settings, 'DIRTYEDIT_USE_REVERSION', True)
14 |
15 | AUTHORIZED_PATHS = getattr(settings, 'DIRTYEDIT_AUTHORIZED_PATHS', authorized_paths)
16 | EXCLUDED_PATHS = getattr(settings, 'DIRTYEDIT_EXCLUDED_PATHS', ())
17 |
18 | CAN_CREATE_FILES = getattr(settings, 'DIRTYEDIT_CAN_CREATE_FILES', False)
--------------------------------------------------------------------------------
/dirtyedit/forms.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django import forms
3 | from codemirror2.widgets import CodeMirrorEditor
4 | from dirtyedit.models import FileToEdit
5 | from dirtyedit.conf import CODEMIRROR_KEYMAP, EDIT_MODE
6 |
7 | if EDIT_MODE == 'html':
8 | from ckeditor_uploader.widgets import CKEditorUploadingWidget
9 |
10 |
11 | class DirtyEditForm(forms.ModelForm):
12 |
13 | def __init__(self, *args, **kwargs):
14 | super(DirtyEditForm, self).__init__(*args, **kwargs)
15 |
16 | if EDIT_MODE == 'html':
17 | content = forms.CharField(widget=CKEditorUploadingWidget())
18 | elif EDIT_MODE == 'code':
19 | content = forms.CharField(
20 | widget=CodeMirrorEditor(options={
21 | 'mode': 'htmlmixed',
22 | 'width': '1170px',
23 | 'indentWithTabs': 'true',
24 | 'indentUnit': '4',
25 | 'lineNumbers': 'true',
26 | 'autofocus': 'true',
27 | #'highlightSelectionMatches': '{showToken: /\w/, annotateScrollbar: true}',
28 | 'styleActiveLine': 'true',
29 | 'autoCloseTags': 'true',
30 | 'keyMap': CODEMIRROR_KEYMAP,
31 | 'theme': 'blackboard',
32 | },
33 | modes=['css', 'xml', 'javascript', 'htmlmixed'],
34 | )
35 |
36 | )
37 | else:
38 | content = forms.CharField(widget=forms.Textarea)
39 | content.required = False
40 |
41 | class Meta:
42 | model = FileToEdit
43 | exclude = ('created', 'edited', 'editor')
44 |
--------------------------------------------------------------------------------
/dirtyedit/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/synw/django-dirtyedit/29b23222471ac07a9c398a9cb4a038189d14624c/dirtyedit/management/__init__.py
--------------------------------------------------------------------------------
/dirtyedit/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/synw/django-dirtyedit/29b23222471ac07a9c398a9cb4a038189d14624c/dirtyedit/management/commands/__init__.py
--------------------------------------------------------------------------------
/dirtyedit/management/commands/populate_editor.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import os
3 | from django.core.management.base import BaseCommand
4 | from ...utils import read_file
5 | from ...msgs import Msgs
6 | from ...models import FileToEdit
7 |
8 |
9 | class Command(BaseCommand, Msgs):
10 | help = 'Populate file instances from a directory for dirtyedit'
11 |
12 | def add_arguments(self, parser):
13 | parser.add_argument('source', type=str)
14 |
15 | def handle(self, *args, **options):
16 | source = options["source"]
17 | # get the files
18 | self.status("Getting files from " + source)
19 | filenames = self.get_filenames(source)
20 | # save instances
21 | for filename in filenames:
22 | path = source + filename
23 | status, msg, filecontent = read_file(path, True)
24 | if status is True:
25 | self.status(msg)
26 | else:
27 | if status == 'warn':
28 | self.error(msg)
29 | elif status == 'infos':
30 | self.info(msg)
31 | self.error(msg)
32 | # save instance
33 | _, created = FileToEdit.objects.get_or_create(
34 | relative_path=path, content=filecontent)
35 | if created is False:
36 | msg = "File " + filename + " already exists in the database"
37 | self.error(msg)
38 | self.ok("Done")
39 |
40 | def get_filenames(self, startpath):
41 | dirfiles = []
42 | for _, _, files in os.walk(startpath):
43 | for filename in files:
44 | print(filename)
45 | dirfiles.append(filename)
46 | return dirfiles
47 |
--------------------------------------------------------------------------------
/dirtyedit/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.6 on 2017-04-03 14:08
3 | from __future__ import unicode_literals
4 |
5 | from django.conf import settings
6 | from django.db import migrations, models
7 | import django.db.models.deletion
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='FileToEdit',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('edited', models.DateTimeField(auto_now=True, verbose_name='Edited')),
24 | ('created', models.DateTimeField(auto_now_add=True)),
25 | ('relative_path', models.CharField(max_length=255, null=True, unique=True, verbose_name='File path')),
26 | ('content', models.TextField(blank=True, null=True)),
27 | ('editor', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Edited by')),
28 | ],
29 | options={
30 | 'verbose_name': 'File to edit',
31 | 'verbose_name_plural': 'Files to edit',
32 | },
33 | ),
34 | ]
35 |
--------------------------------------------------------------------------------
/dirtyedit/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/synw/django-dirtyedit/29b23222471ac07a9c398a9cb4a038189d14624c/dirtyedit/migrations/__init__.py
--------------------------------------------------------------------------------
/dirtyedit/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf import settings
4 | from django.db import models
5 | from django.utils.translation import ugettext_lazy as _
6 |
7 |
8 | class FileToEdit(models.Model):
9 | edited = models.DateTimeField(editable=False, auto_now=True, verbose_name=_(u'Edited'))
10 | created = models.DateTimeField(editable=False, auto_now_add=True)
11 | editor = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, related_name='+', null=True, on_delete=models.SET_NULL, verbose_name=u'Edited by')
12 | relative_path = models.CharField(max_length=255, null=True, unique=True, verbose_name=_(u"File path"))
13 | # to select the mode in codemirror: must figure out how to access this field value in admin.ModelAdmin first
14 | #file_type = models.CharField(max_length=60, blank=True, help_text=_(u'See here for a list: http://codemirror.net/mode/'))
15 | content = models.TextField(null=True, blank=True)
16 |
17 |
18 | class Meta:
19 | verbose_name=_(u'File to edit')
20 | verbose_name_plural = _(u'Files to edit')
21 |
22 | def __str__(self):
23 | return str(self.relative_path)
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/dirtyedit/msgs.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -
2 |
3 |
4 | class Msgs():
5 | """
6 | A class to handle application messages
7 | """
8 |
9 | def ok(self, msg):
10 | """
11 | Prints an error message
12 | """
13 | err = "[" + self.style.SUCCESS("Ok") + "] " + msg
14 | self.stdout.write(err)
15 |
16 | def error(self, msg):
17 | """
18 | Prints an error message
19 | """
20 | err = "[" + self.style.ERROR("Error") + "] " + msg
21 | self.stdout.write(err)
22 |
23 | def status(self, msg):
24 | """
25 | Prints an info message
26 | """
27 | err = "[" + self.style.HTTP_NOT_FOUND("Status") + "] " + msg
28 | self.stdout.write(err)
29 |
30 | def info(self, msg):
31 | """
32 | Prints an info message
33 | """
34 | err = "[" + self.style.HTTP_NOT_MODIFIED("Info") + "] " + msg
35 | self.stdout.write(err)
36 |
--------------------------------------------------------------------------------
/dirtyedit/templates/admin/dirtyedit/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 | {% load i18n admin_urls static admin_modify %}
3 |
4 | {% block extrahead %}{{ block.super }}
5 |
6 | {{ media }}
7 | {% endblock %}
8 |
9 | {% block extrastyle %}{{ block.super }}{% endblock %}
10 |
11 | {% block coltype %}colM{% endblock %}
12 |
13 | {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
14 |
15 | {% block messages %}
16 | {% if messages %}
17 |
{% for message in messages %}
18 | - {{ message|safe|capfirst }}
19 | {% endfor %}
20 | {% endif %}
21 | {% endblock messages %}
22 |
23 | {% if not is_popup %}
24 | {% block breadcrumbs %}
25 |
31 | {% endblock %}
32 | {% endif %}
33 |
34 | {% block content %}
35 | {% block object-tools %}
36 | {% if change %}{% if not is_popup %}
37 |
46 | {% endif %}{% endif %}
47 | {% endblock %}
48 |
111 | {% endblock %}
112 |
--------------------------------------------------------------------------------
/dirtyedit/templates/admin/dirtyedit/change_list.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 | {% load i18n admin_urls static admin_list %}
3 |
4 | {% block extrastyle %}
5 | {{ block.super }}
6 |
7 | {% if cl.formset %}
8 |
9 | {% endif %}
10 | {% if cl.formset or action_form %}
11 |
12 | {% endif %}
13 | {{ media.css }}
14 | {% if not actions_on_top and not actions_on_bottom %}
15 |
18 | {% endif %}
19 | {% endblock %}
20 |
21 | {% block extrahead %}
22 | {{ block.super }}
23 | {{ media.js }}
24 | {% endblock %}
25 |
26 | {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
27 |
28 | {% if not is_popup %}
29 | {% block breadcrumbs %}
30 |
35 | {% endblock %}
36 | {% endif %}
37 |
38 | {% block coltype %}flex{% endblock %}
39 |
40 | {% block content %}
41 |
42 | {% block object-tools %}
43 |
73 | {% endblock %}
74 | {% if cl.formset.errors %}
75 |
76 | {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
77 |
78 | {{ cl.formset.non_form_errors }}
79 | {% endif %}
80 |
81 | {% block search %}{% search_form cl %}{% endblock %}
82 | {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
83 |
84 | {% block filters %}
85 | {% if cl.has_filters %}
86 |
87 |
{% trans 'Filter' %}
88 | {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
89 |
90 | {% endif %}
91 | {% endblock %}
92 |
93 |
105 |
106 |
107 | {% endblock %}
108 |
--------------------------------------------------------------------------------
/dirtyedit/templates/admin/dirtyedit/includes/fieldset.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dirtyedit/templates/codemirror2/codemirror_script.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/dirtyedit/templates/reversion/change_list.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/dirtyedit/change_list.html" %}
2 | {# dirty trick to print out the add file form: check dirtyedit/change_list.html #}
3 | {% load i18n admin_urls %}
4 |
5 |
6 | {% block object-tools-items %}
7 | {% if not is_popup and has_add_permission and has_change_permission %}
8 | {% blocktrans with cl.opts.verbose_name_plural|escape as name %}Recover deleted {{name}}{% endblocktrans %}
9 | {% endif %}
10 | {{block.super}}
11 | {% endblock %}
--------------------------------------------------------------------------------
/dirtyedit/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/dirtyedit/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | from django.conf import settings
5 | from django.utils._os import safe_join
6 | from django.utils.translation import ugettext as _
7 | from dirtyedit.conf import AUTHORIZED_PATHS, EXCLUDED_PATHS, CAN_CREATE_FILES
8 |
9 | filepath_form = """
10 |
15 |
25 | """
26 |
27 |
28 | def check_file(relative_path, edit_mode=False, dir_only=False):
29 | """
30 | Checks the if the file is editable
31 | """
32 | ok, msg = check_path(relative_path, edit_mode, dir_only)
33 | if ok is False:
34 | return False, msg
35 | return True, ''
36 |
37 |
38 | def check_path(relative_path, edit_mode=False, dir_only=False):
39 | """
40 | Does some security checks on filepath
41 | """
42 | # check for empty path
43 | if relative_path == '':
44 | msg = filepath_form + \
45 | _(u"Please provide a file path
")
46 | return False, msg
47 | # check for root path
48 | if relative_path == '/':
49 | msg = filepath_form + _(u'What?
')
50 | return False, msg
51 | # check for filename
52 | if relative_path.endswith('/') and dir_only is False:
53 | print("jjjjjjjjjjj", dir_only)
54 | msg = filepath_form + \
55 | _(u"Path '%s' is invalid: "
56 | "please provide a filename
") % (relative_path,)
57 | return False, msg
58 | pathlist = relative_path.split('/')
59 | folderpath = '/'.join(pathlist[:len(pathlist) - 1])
60 | # check for excluded paths
61 | for fpath in EXCLUDED_PATHS:
62 | if relative_path.startswith(fpath):
63 | msg = filepath_form + \
64 | _(u"You can not edit files in the directory "
65 | "'%s'
") % (folderpath,)
66 | return (False, msg)
67 | # check vs authorized paths
68 | is_authorized = False
69 | for authorized_path in AUTHORIZED_PATHS:
70 | #~ check if the path is part of the authorized path
71 | if folderpath.startswith(authorized_path):
72 | # '+folderpath
73 | is_authorized = True
74 | break
75 | if is_authorized is False:
76 | msg = filepath_form + \
77 | _(u"You can not edit files in the directory "
78 | "'%s'
") % (folderpath,)
79 | return (False, msg)
80 | # verify that the directory exists and is under project root
81 | absolute_folderpath = safe_join(settings.BASE_DIR, folderpath)
82 | if not os.path.isdir(absolute_folderpath):
83 | msg = filepath_form + \
84 | _(u"The directory %s'"
85 | " does not exist
") % (folderpath,)
86 | return (False, msg)
87 | # check if file exists
88 | if dir_only is False:
89 | filepath = safe_join(settings.BASE_DIR, relative_path)
90 | if not os.path.isfile(filepath):
91 | # msgs
92 | if CAN_CREATE_FILES is True:
93 | if not edit_mode is True:
94 | msg = _(
95 | u"A new file will be created at "
96 | "'%s'
") % (relative_path,)
97 | return ('infos', msg)
98 | else:
99 | if not edit_mode is True:
100 | msg = filepath_form + \
101 | _(u"File '%s' "
102 | "not found
") % (relative_path,)
103 | else:
104 | msg = filepath_form + \
105 | _(u"You can not create files
")
106 | return (False, msg)
107 | # ok
108 | return True, ''
109 |
110 |
111 | def read_file(relative_path, dir_only=False):
112 | status, msg = check_file(relative_path, False, dir_only)
113 | if status in [False, 'warn', 'infos']:
114 | return (status, msg, None)
115 | # read file
116 | filepath = safe_join(settings.BASE_DIR, relative_path)
117 | filex = open(filepath, "r")
118 | filecontent = filex.read()
119 | msg = _(u"File " + filepath + " found: data populated")
120 | return (True, msg, filecontent)
121 |
122 |
123 | def write_file(relative_path, content):
124 | status, msg = check_file(relative_path, edit_mode=True)
125 | if status in [False, 'warn', 'infos']:
126 | return (status, msg)
127 | else:
128 | filepath = safe_join(settings.BASE_DIR, relative_path)
129 | #~ write the file
130 | filex = open(filepath, "w")
131 | filex.write(content)
132 | filex.close()
133 | return (True, msg)
134 |
--------------------------------------------------------------------------------
/docs/img/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/synw/django-dirtyedit/29b23222471ac07a9c398a9cb4a038189d14624c/docs/img/screenshot1.png
--------------------------------------------------------------------------------
/docs/img/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/synw/django-dirtyedit/29b23222471ac07a9c398a9cb4a038189d14624c/docs/img/screenshot2.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | django-ckeditor
2 | django-codemirror2
3 | django-reversion
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 |
4 | version = __import__('dirtyedit').__version__
5 |
6 | setup(
7 | name = 'django-dirtyedit',
8 | packages=find_packages(),
9 | include_package_data=True,
10 | version = version,
11 | description = ' Django application to edit files from the admin interface',
12 | author = 'synw',
13 | author_email = 'synwe@yahoo.com',
14 | url = 'https://github.com/synw/django-dirtyedit',
15 | download_url = 'https://github.com/synw/django-dirtyedit/releases/tag/'+version,
16 | keywords = ['django', 'editor'],
17 | classifiers = [
18 | 'Development Status :: 3 - Alpha',
19 | 'Framework :: Django :: 1.8',
20 | 'Intended Audience :: Developers',
21 | 'License :: OSI Approved :: MIT License',
22 | 'Programming Language :: Python :: 2.7',
23 | ],
24 | install_requires=[
25 | 'django-ckeditor',
26 | 'django-codemirror2',
27 | 'django-reversion'
28 | ],
29 | zip_safe=False
30 | )
31 |
--------------------------------------------------------------------------------