diff --git a/cookbook/forms.py b/cookbook/forms.py
index 0c85bab3..a57c6b8c 100644
--- a/cookbook/forms.py
+++ b/cookbook/forms.py
@@ -17,7 +17,7 @@ class EmojiWidget(forms.TextInput):
class EditRecipeForm(forms.ModelForm):
class Meta:
model = Recipe
- fields = ('name', 'category', 'keywords', 'path')
+ fields = ('name', 'category', 'keywords', 'path', 'storage')
labels = {
'name': _('Name'),
@@ -42,8 +42,20 @@ class KeywordForm(forms.ModelForm):
widgets = {'icon': EmojiWidget}
-class MonitorForm(forms.Form):
- path = forms.CharField(label=_('Path'))
+class StorageForm(forms.ModelForm):
+ username = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), required=False)
+ password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}), required=False)
+ token = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}), required=False)
+
+ class Meta:
+ model = Storage
+ fields = ('name', 'method', 'username', 'password', 'token', 'url')
+
+
+class SyncForm(forms.ModelForm):
+ class Meta:
+ model = Sync
+ fields = ('storage', 'path')
class BatchEditForm(forms.Form):
diff --git a/cookbook/helper/dropbox.py b/cookbook/helper/dropbox.py
index c8aed082..672af489 100644
--- a/cookbook/helper/dropbox.py
+++ b/cookbook/helper/dropbox.py
@@ -8,22 +8,11 @@ from django.conf import settings
from cookbook.models import Recipe, Sync, RecipeImport, SyncLog
-def sync_all():
- monitors = Sync.objects.all()
-
- for monitor in monitors:
- ret = import_all(monitor)
- if not ret:
- return ret
-
- return True
-
-
def import_all(monitor):
url = "https://api.dropboxapi.com/2/files/list_folder"
headers = {
- "Authorization": "Bearer " + settings.DROPBOX_API_KEY,
+ "Authorization": "Bearer " + monitor.storage.token,
"Content-Type": "application/json"
}
@@ -40,15 +29,15 @@ def import_all(monitor):
return r
import_count = 0
- for recipe in recipes['entries']:
+ for recipe in recipes['entries']: # TODO check if has_more is set and import that as well
path = recipe['path_lower']
if not Recipe.objects.filter(path=path).exists() and not RecipeImport.objects.filter(path=path).exists():
name = os.path.splitext(recipe['name'])[0]
- new_recipe = RecipeImport(name=name, path=path)
+ new_recipe = RecipeImport(name=name, path=path, storage=monitor.storage)
new_recipe.save()
import_count += 1
- log_entry = SyncLog(status='SUCCESS', msg='Imported ' + str(import_count) + ' recipes', monitor=monitor)
+ log_entry = SyncLog(status='SUCCESS', msg='Imported ' + str(import_count) + ' recipes', sync=monitor)
log_entry.save()
monitor.last_checked = datetime.now()
diff --git a/cookbook/migrations/0001_initial.py b/cookbook/migrations/0001_initial.py
index 05e513cc..0ee34575 100644
--- a/cookbook/migrations/0001_initial.py
+++ b/cookbook/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.0.5 on 2018-05-14 08:50
+# Generated by Django 2.0.5 on 2018-05-25 13:11
from django.db import migrations, models
import django.db.models.deletion
@@ -17,6 +17,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, unique=True)),
+ ('icon', models.CharField(blank=True, max_length=1, null=True)),
('description', models.TextField(blank=True, default='')),
('created_by', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
@@ -28,6 +29,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, unique=True)),
+ ('icon', models.CharField(blank=True, max_length=1, null=True)),
('description', models.TextField(blank=True, default='')),
('created_by', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
@@ -44,7 +46,7 @@ class Migration(migrations.Migration):
('created_by', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
- ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Category')),
+ ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.Category')),
('keywords', models.ManyToManyField(blank=True, to='cookbook.Keyword')),
],
),
@@ -57,6 +59,18 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
+ migrations.CreateModel(
+ name='Storage',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=128)),
+ ('method', models.CharField(choices=[('DB', 'Dropbox')], default='DB', max_length=128)),
+ ('username', models.CharField(blank=True, max_length=128, null=True)),
+ ('password', models.CharField(blank=True, max_length=128, null=True)),
+ ('token', models.CharField(blank=True, max_length=512, null=True)),
+ ('url', models.URLField(blank=True, null=True)),
+ ],
+ ),
migrations.CreateModel(
name='Sync',
fields=[
@@ -65,6 +79,7 @@ class Migration(migrations.Migration):
('last_checked', models.DateTimeField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
+ ('storage', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Storage')),
],
),
migrations.CreateModel(
@@ -77,4 +92,9 @@ class Migration(migrations.Migration):
('monitor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Sync')),
],
),
+ migrations.AddField(
+ model_name='recipe',
+ name='storage',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Storage'),
+ ),
]
diff --git a/cookbook/migrations/0002_auto_20180525_1558.py b/cookbook/migrations/0002_auto_20180525_1558.py
new file mode 100644
index 00000000..2d78e127
--- /dev/null
+++ b/cookbook/migrations/0002_auto_20180525_1558.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.5 on 2018-05-25 13:58
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cookbook', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='synclog',
+ old_name='monitor',
+ new_name='sync',
+ ),
+ ]
diff --git a/cookbook/migrations/0002_category_icon.py b/cookbook/migrations/0002_category_icon.py
deleted file mode 100644
index 315045aa..00000000
--- a/cookbook/migrations/0002_category_icon.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.0.5 on 2018-05-14 08:58
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('cookbook', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='category',
- name='icon',
- field=models.CharField(blank=True, max_length=4, null=True),
- ),
- ]
diff --git a/cookbook/migrations/0003_auto_20180514_1121.py b/cookbook/migrations/0003_auto_20180514_1121.py
deleted file mode 100644
index d24492fc..00000000
--- a/cookbook/migrations/0003_auto_20180514_1121.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.0.5 on 2018-05-14 09:21
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('cookbook', '0002_category_icon'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='keyword',
- name='icon',
- field=models.CharField(blank=True, max_length=1, null=True),
- ),
- migrations.AlterField(
- model_name='category',
- name='icon',
- field=models.CharField(blank=True, max_length=1, null=True),
- ),
- ]
diff --git a/cookbook/migrations/0003_recipeimport_storage.py b/cookbook/migrations/0003_recipeimport_storage.py
new file mode 100644
index 00000000..4c2d523d
--- /dev/null
+++ b/cookbook/migrations/0003_recipeimport_storage.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.0.5 on 2018-05-25 14:05
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cookbook', '0002_auto_20180525_1558'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='recipeimport',
+ name='storage',
+ field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Storage'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/cookbook/migrations/0004_auto_20180515_2035.py b/cookbook/migrations/0004_auto_20180515_2035.py
deleted file mode 100644
index 3696d4c8..00000000
--- a/cookbook/migrations/0004_auto_20180515_2035.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 2.0.5 on 2018-05-15 18:35
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('cookbook', '0003_auto_20180514_1121'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='recipe',
- name='category',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.Category'),
- ),
- ]
diff --git a/cookbook/models.py b/cookbook/models.py
index 4f9fa6a4..35bfa27f 100644
--- a/cookbook/models.py
+++ b/cookbook/models.py
@@ -1,6 +1,36 @@
from django.db import models
+class Storage(models.Model):
+ DROPBOX = 'DB'
+ STORAGE_TYPES = ((DROPBOX, 'Dropbox'),)
+
+ name = models.CharField(max_length=128)
+ method = models.CharField(choices=STORAGE_TYPES, max_length=128, default=DROPBOX)
+ username = models.CharField(max_length=128, blank=True, null=True)
+ password = models.CharField(max_length=128, blank=True, null=True)
+ token = models.CharField(max_length=512, blank=True, null=True)
+ url = models.URLField(blank=True, null=True)
+
+ def __str__(self):
+ return self.name
+
+
+class Sync(models.Model):
+ storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
+ path = models.CharField(max_length=512, default="")
+ last_checked = models.DateTimeField()
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+
+class SyncLog(models.Model):
+ sync = models.ForeignKey(Sync, on_delete=models.CASCADE)
+ status = models.CharField(max_length=32)
+ msg = models.TextField(default="")
+ created_at = models.DateTimeField(auto_now_add=True)
+
+
class Keyword(models.Model):
name = models.CharField(max_length=64, unique=True)
icon = models.CharField(max_length=1, blank=True, null=True)
@@ -28,6 +58,7 @@ class Category(models.Model):
class Recipe(models.Model):
name = models.CharField(max_length=128)
path = models.CharField(max_length=512, default="")
+ storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
link = models.CharField(max_length=512, default="")
category = models.ForeignKey(Category, blank=True, on_delete=models.SET_NULL, null=True)
keywords = models.ManyToManyField(Keyword, blank=True)
@@ -45,19 +76,6 @@ class Recipe(models.Model):
class RecipeImport(models.Model):
name = models.CharField(max_length=128)
+ storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
path = models.CharField(max_length=512, default="")
created_at = models.DateTimeField(auto_now_add=True)
-
-
-class Sync(models.Model):
- path = models.CharField(max_length=512, default="")
- last_checked = models.DateTimeField()
- created_at = models.DateTimeField(auto_now_add=True)
- updated_at = models.DateTimeField(auto_now=True)
-
-
-class SyncLog(models.Model):
- monitor = models.ForeignKey(Sync, on_delete=models.CASCADE)
- status = models.CharField(max_length=32)
- msg = models.TextField(default="")
- created_at = models.DateTimeField(auto_now_add=True)
diff --git a/cookbook/tables.py b/cookbook/tables.py
index bc1979b4..89bbdffb 100644
--- a/cookbook/tables.py
+++ b/cookbook/tables.py
@@ -39,8 +39,17 @@ class KeywordTable(tables.Table):
fields = ('id', 'icon', 'name')
+class StorageTable(tables.Table):
+ id = tables.LinkColumn('edit_storage', args=[A('id')])
+
+ class Meta:
+ model = Storage
+ template_name = 'generic/table_template.html'
+ fields = ('id', 'name', 'method')
+
+
class ImportLogTable(tables.Table):
- monitor_id = tables.LinkColumn('edit_monitor', args=[A('monitor_id')])
+ sync_id = tables.LinkColumn('edit_sync', args=[A('sync_id')])
@staticmethod
def render_status(value):
@@ -52,20 +61,24 @@ class ImportLogTable(tables.Table):
class Meta:
model = SyncLog
template_name = 'generic/table_template.html'
- fields = ('status', 'msg', 'monitor_id', 'created_at')
+ fields = ('status', 'msg', 'sync_id', 'created_at')
-class MonitoredPathTable(tables.Table):
- id = tables.LinkColumn('edit_monitor', args=[A('id')])
+class SyncTable(tables.Table):
+ id = tables.LinkColumn('edit_sync', args=[A('id')])
@staticmethod
def render_path(value):
return format_html('%s
' % value)
+ @staticmethod
+ def render_storage(value):
+ return format_html('%s' % value)
+
class Meta:
model = Sync
template_name = 'generic/table_template.html'
- fields = ('id', 'path', 'last_checked')
+ fields = ('id', 'path', 'storage', 'last_checked')
class RecipeImportTable(tables.Table):
diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html
index 8e5944ad..9f238472 100644
--- a/cookbook/templates/base.html
+++ b/cookbook/templates/base.html
@@ -65,6 +65,8 @@
class="fas fa-archive"> {% trans 'Category' %}
{% trans 'Keyword' %}
+ {% trans 'Storage Backend' %}
{% blocktrans %}
+ The Password and Token field are stored as plain text inside the database.
+ This is necessary because they are needed to make API requests, but it also increases the risk of someone stealing it.
+ To limit the possible damage use read only tokens or accounts if available or create separate accounts with limited access (only to recipes).
+ {% endblocktrans %}