Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/yaml in git #47

Open
wants to merge 10 commits into
base: miller-v2
Choose a base branch
from
3 changes: 3 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ STATIC_URL=/miller-assets/
MILLER_SCHEMA_ROOT=/contents/schema
DEBUG=False
LANGUAGES=en|American English|en_US|english,fr|French|fr_FR|french,de|German|de_DE|german
MILLER_CONTENTS_ENABLE_GIT=True
MILLER_CONTENTS_GIT_USERNAME=your-username-for-git
MILLER_CONTENTS_GIT_EMAIL=your-email-for-git
2 changes: 2 additions & 0 deletions docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ services:
MILLER_DATABASE_HOST: postgresdb
MILLER_DATABASE_PORT: 5432
MILLER_SCHEMA_ROOT: ${MILLER_SCHEMA_ROOT}
MILLER_CONTENTS_ENABLE_GIT: ${MILLER_CONTENTS_ENABLE_GIT}
MILLER_GIT_TAG: ${GIT_TAG}
MILLER_GIT_BRANCH: ${GIT_BRANCH}
MILLER_GIT_REVISION: ${GIT_REVISION}
Expand Down Expand Up @@ -64,6 +65,7 @@ services:
MILLER_DATABASE_HOST: postgresdb
MILLER_DATABASE_PORT: 5432
MILLER_SCHEMA_ROOT: ${MILLER_SCHEMA_ROOT}
MILLER_CONTENTS_ENABLE_GIT: ${MILLER_CONTENTS_ENABLE_GIT}
MILLER_GIT_TAG: ${GIT_TAG}
MILLER_GIT_BRANCH: ${GIT_BRANCH}
MILLER_GIT_REVISION: ${GIT_REVISION}
Expand Down
46 changes: 46 additions & 0 deletions miller/management/commands/document_commit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from miller.models import Document
from miller.tasks import commit_document

class Command(BaseCommand):
"""
usage:
ENV=development pipenv run ./manage.py document_commit <pks>
or if in docker:
docker exec -it docker_miller_1 \
python manage.py document_commit \
<document_pk>, <document_pk> ... [--immediate] [--verbose]
"""
help = 'save YAML version of documents and commit them (if a git repo is enaled)'

def add_arguments(self, parser):
parser.add_argument('document_pks', nargs='+', type=int)
parser.add_argument(
'--immediate',
action='store_true',
help='avoid delay tasks using celery',
)
parser.add_argument(
'--verbose',
action='store_true',
help='use verbose logging',
)

def handle(
self, document_pks, immediate=False, verbose=False, *args, **options
):
if not settings.MILLER_CONTENTS_ENABLE_GIT:
self.stderr.write('MILLER_CONTENTS_ENABLE_GIT not enabled!')
# repo = get_repo()
self.stdout.write(f'document_commit for: {document_pks}')
docs = Document.objects.filter(pk__in=document_pks)
# loop through documents
for doc in docs:
try:
if immediate:
doc.commit()
else:
commit_document.delay(document_pk=doc.pk)
except Exception as e:
self.stderr.write(e)
46 changes: 46 additions & 0 deletions miller/management/commands/story_commit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from miller.models import Story
from miller.tasks import commit_story

class Command(BaseCommand):
"""
usage:
ENV=development pipenv run ./manage.py story_commit <pks>
or if in docker:
docker exec -it docker_miller_1 \
python manage.py story_commit \
<story_pk>, <story_pk> ... [--immediate] [--verbose]
"""
help = 'save YAML version of documents and commit them (if a git repo is enaled)'

def add_arguments(self, parser):
parser.add_argument('story_pks', nargs='+', type=int)
parser.add_argument(
'--immediate',
action='store_true',
help='avoid delay tasks using celery',
)
parser.add_argument(
'--verbose',
action='store_true',
help='use verbose logging',
)

def handle(
self, story_pks, immediate=False, verbose=False, *args, **options
):
if not settings.MILLER_CONTENTS_ENABLE_GIT:
self.stderr.write('MILLER_CONTENTS_ENABLE_GIT not enabled!')
# repo = get_repo()
self.stdout.write(f'story_commit for: {story_pks}')
docs = Story.objects.filter(pk__in=story_pks)
# loop through documents
for doc in docs:
try:
if immediate:
doc.commit()
else:
commit_story.delay(story_pk=doc.pk)
except Exception as e:
self.stderr.write(e)
7 changes: 6 additions & 1 deletion miller/models/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..fields import UTF8JSONField
from ..snapshots import create_snapshot, create_different_sizes_from_snapshot
from ..utils.models import get_search_vector_query, create_short_url
from ..utils.git import commit_instance
from ..utils.media import get_video_subtitles


Expand Down Expand Up @@ -271,4 +272,8 @@ def update_search_vector(self, verbose=False):
for value, w, c in contents
] + [self.pk])


def commit(self):
"""
if settings.MILLER_CONTENTS_ENABLE_GIT, write to disk
"""
commit_instance(instance=self)
9 changes: 8 additions & 1 deletion miller/models/story.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from . import Tag
from ..utils import get_all_values_from_dict_by_key
from ..utils.models import get_user_path, create_short_url, get_unique_slug
from ..utils.git import commit_instance
from ..fields import UTF8JSONField


Expand Down Expand Up @@ -93,7 +94,7 @@ class Story(models.Model):

# add huge search field
search_vector = SearchVectorField(null=True, blank=True)

# enable full text search using postgres vectors stored in search_vector
allow_fulltext_search = True

Expand Down Expand Up @@ -152,3 +153,9 @@ def save_captions_from_contents(self, key='pk', parser='json'):
# save captions
saved = ThroughModel.objects.bulk_create([ThroughModel(document=d, story=self) for d in docs])
return saved, missing, expecting

def commit(self):
"""
if settings.MILLER_CONTENTS_ENABLE_GIT, write to disk
"""
commit_instance(instance=self)
2 changes: 2 additions & 0 deletions miller/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@
MILLER_CONTENTS_ROOT = get_env_variable('CONTENTS_ROOT', '/contents')
MILLER_CONTENTS_ENABLE_GIT = get_env_variable(
'MILLER_CONTENTS_ENABLE_GIT', 'True') == 'True'
MILLER_CONTENTS_GIT_USERNAME = get_env_variable('MILLER_CONTENTS_GIT_USERNAME', 'miller')
MILLER_CONTENTS_GIT_EMAIL = get_env_variable('MILLER_CONTENTS_GIT_EMAIL', 'donotreply@miller')
# snapshots and thumbnail sizes
# default: max size, both heght and width must be 1200 px
MILLER_SIZES_SNAPSHOT = [
Expand Down
27 changes: 26 additions & 1 deletion miller/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,32 @@ def update_document_search_vectors(self, document_pk, verbose=False):
)


@app.task(
bind=True, autoretry_for=(Exception,), exponential_backoff=2,
retry_kwargs={'max_retries': 5}, retry_jitter=True
)
def commit_document(self, document_pk, verbose=False):
logger.info(f'commit_document document(pk={document_pk})')
doc = Document.objects.get(pk=document_pk)
doc.commit()
logger.info(
f'commit_document document(pk={document_pk}) success.'
)


@app.task(
bind=True, autoretry_for=(Exception,), exponential_backoff=2,
retry_kwargs={'max_retries': 5}, retry_jitter=True
)
def commit_story(self, story_pk, verbose=False):
logger.info(f'commit_story document(pk={story_pk})')
story = Story.objects.get(pk=story_pk)
story.commit()
logger.info(
f'commit_story document(pk={story_pk}) success.'
)


@app.task(
bind=True, autoretry_for=(Exception,), exponential_backoff=2,
retry_kwargs={'max_retries': 5}, retry_jitter=True
Expand All @@ -62,4 +88,3 @@ def update_document_data_by_type(self, document_pk):
def document_post_save_handler(sender, instance, **kwargs):
logger.info(f'received @post_save document_pk: {instance.pk}')
update_document_search_vectors.delay(document_pk=instance.pk)

61 changes: 61 additions & 0 deletions miller/utils/git.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os
import logging
from git import Repo, Actor
from django.conf import settings
from django.core import serializers

logger = logging.getLogger(__name__)

def get_repo():
if not settings.MILLER_CONTENTS_ENABLE_GIT:
raise NameError('You shoud enable MILLER_CONTENTS_ENABLE_GIT in the settings file')
if not settings.MILLER_CONTENTS_ROOT:
raise NameError('You shoud set MILLER_CONTENTS_ROOT to an absolute path in the settings file')
if not os.path.exists(settings.MILLER_CONTENTS_ROOT):
raise OsError(f'path for MILLER_CONTENTS_ROOT does not exist or it is not reachable: {settings.MILLER_CONTENTS_ROOT}') # noqa: F821
repo = Repo.init(settings.MILLER_CONTENTS_ROOT)
return repo

def get_or_create_path_in_contents_root(folder_path):
prefixed_path = os.path.join(
settings.MILLER_CONTENTS_ROOT,
folder_path
)
if not os.path.exists(prefixed_path):
os.makedirs(prefixed_path)
with open(os.path.join(prefixed_path, '.gitkeep'), 'w'):
pass
return prefixed_path

def commit_filepath(filepath, username, email, message):
author = Actor(username, email)
committer = Actor(username, email)
repo = get_repo()
repo.index.add([filepath])
commit_message = repo.index.commit(message=message, author=author, committer=committer)
short_sha = repo.git.rev_parse(commit_message, short=7)
return short_sha


def commit_instance(
instance,
verbose=False,
serializer='yaml'
):
logger.info(
f'commit_instance for instance pk:{instance.pk} model:{instance._meta.model.__name__}'
)
folder_path = get_or_create_path_in_contents_root(folder_path=instance._meta.model.__name__)
contents = serializers.serialize(serializer, [instance])
filepath = os.path.join(folder_path, f'{instance.pk}-{instance.short_url}.yml')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(contents)
logger.info(f'commit_instance document: {instance.pk} {instance.short_url} written to {filepath}')
if settings.MILLER_CONTENTS_ENABLE_GIT:
short_sha = commit_filepath(
filepath=filepath,
username=instance.owner if instance.owner is not None else settings.MILLER_CONTENTS_GIT_USERNAME,
email=settings.MILLER_CONTENTS_GIT_EMAIL,
message=f'saving {instance.title}'
)
logger.info(f'commit_instance document: {instance.pk} {instance.short_url} committed: {short_sha}')