301 lines
12 KiB
Python
301 lines
12 KiB
Python
import logging
|
|
import re
|
|
|
|
import os
|
|
from django.conf import settings
|
|
from django.db.models import Q
|
|
from django.forms import FileField, ImageField, ClearableFileInput, CharField
|
|
from django.http import Http404, HttpResponseRedirect, HttpResponseServerError, JsonResponse
|
|
from django.urls import reverse_lazy
|
|
from django.utils import formats, translation
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.utils.translation.trans_real import language_code_re
|
|
from django.views.generic import DetailView
|
|
from django.views.generic.base import TemplateResponseMixin
|
|
from django.views.generic.edit import BaseUpdateView, DeleteView
|
|
from modeltranslation.forms import TranslationModelForm
|
|
from modeltranslation import settings as modeltranslation_settings
|
|
from modeltranslation.utils import get_language
|
|
from tinymce.widgets import TinyMCE
|
|
|
|
from repomaker.models import App, ApkPointer, Screenshot
|
|
from repomaker.models.category import Category
|
|
from repomaker.models.screenshot import PHONE
|
|
from . import DataListTextInput, LanguageMixin
|
|
from .repository import RepositoryAuthorizationMixin, ApkUploadMixin
|
|
|
|
|
|
class MDLTinyMCE(TinyMCE):
|
|
"""
|
|
Ugly hack to work around a conflict between MDL and TinyMCE. See #31 for more details.
|
|
Also removes the requirement for language packs.
|
|
"""
|
|
|
|
def get_mce_config(self, attrs):
|
|
mce_config = super().get_mce_config(attrs)
|
|
if 'language' in mce_config:
|
|
# remove language, so no language pack will be loaded
|
|
del mce_config['language']
|
|
return mce_config
|
|
|
|
def _media(self):
|
|
# we include this manually, so we can decide what gets compressed and what not
|
|
return ()
|
|
|
|
media = property(_media)
|
|
|
|
|
|
class AppDetailView(RepositoryAuthorizationMixin, LanguageMixin, DetailView):
|
|
model = App
|
|
pk_url_kwarg = 'app_id'
|
|
context_object_name = 'app'
|
|
template_name = 'repomaker/app/index.html'
|
|
|
|
def get_repo(self):
|
|
return self.get_object().repo
|
|
|
|
def get_context_data(self, **kwargs):
|
|
self.activate_language()
|
|
context = super(AppDetailView, self).get_context_data(**kwargs)
|
|
app = context['app']
|
|
context['screenshots'] = Screenshot.objects.filter(app=app, type=PHONE,
|
|
language_code=self.get_language())
|
|
context['apks'] = ApkPointer.objects.filter(app=app).order_by('-apk__version_code')
|
|
return context
|
|
|
|
|
|
class AppForm(TranslationModelForm):
|
|
screenshots = ImageField(required=False, widget=ClearableFileInput(attrs={'multiple': True}))
|
|
apks = FileField(required=False, widget=ClearableFileInput(attrs={'multiple': True}))
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.queryset = queryset = App.objects.all()
|
|
super(AppForm, self).__init__(*args, **kwargs)
|
|
if self.instance.category:
|
|
# Show only own and default categories
|
|
self.fields['category'].queryset = Category.objects.filter(
|
|
Q(user=None) | Q(user=self.instance.repo.user))
|
|
|
|
def save(self, commit=True):
|
|
# remove old feature graphic if there was one
|
|
if 'feature_graphic' in self.initial and self.initial['feature_graphic'].name \
|
|
and 'feature_graphic' in self.changed_data:
|
|
old_graphic = self.initial['feature_graphic'].path
|
|
if os.path.exists(old_graphic):
|
|
os.remove(old_graphic)
|
|
return super(AppForm, self).save(commit)
|
|
|
|
class Meta:
|
|
model = App
|
|
fields = ['summary', 'summary_override', 'description', 'description_override',
|
|
'author_name', 'website', 'category', 'screenshots', 'feature_graphic', 'apks']
|
|
widgets = {'description': MDLTinyMCE(), 'description_override': MDLTinyMCE()}
|
|
|
|
|
|
class AppEditView(ApkUploadMixin, LanguageMixin, TemplateResponseMixin, BaseUpdateView):
|
|
model = App
|
|
object = None
|
|
pk_url_kwarg = 'app_id'
|
|
context_object_name = 'app'
|
|
template_name = 'repomaker/app/edit.html'
|
|
form_class = AppForm
|
|
|
|
def get_repo(self):
|
|
return self.get_object().repo
|
|
|
|
def get_context_data(self, **kwargs):
|
|
app = self.get_object()
|
|
self.activate_language()
|
|
language = get_language()
|
|
if language not in app.get_available_languages():
|
|
raise Http404()
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
context['screenshots'] = Screenshot.objects.filter(app=self.get_object(),
|
|
type=PHONE,
|
|
language_code=self.get_language())
|
|
context['apks'] = ApkPointer.objects.filter(app=self.object).order_by('-apk__version_code')
|
|
if self.get_object().tracked_remote:
|
|
# do not allow edits as long as a remote app is tracked
|
|
self.template_name = 'repomaker/app/edit_blocked.html'
|
|
return context
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if 'apks' in self.request.FILES:
|
|
app = self.get_object()
|
|
added_apks = self.add_apks(app)
|
|
if len(added_apks['failed']) > 0:
|
|
if self.request.is_ajax():
|
|
return HttpResponseServerError(self.get_error_msg(added_apks['failed']))
|
|
self.object = app
|
|
form = self.get_form()
|
|
form.add_error('apks', self.get_error_msg(added_apks['failed']))
|
|
return self.form_invalid(form)
|
|
if self.request.is_ajax():
|
|
apk_objects = added_apks['apks']
|
|
apks = []
|
|
for apk in apk_objects:
|
|
apk_dict = {
|
|
'id': ApkPointer.objects.get(app=app, apk=apk).id,
|
|
'version': _('Version %(version)s (%(code)s)') % {
|
|
'version': apk.version_name,
|
|
'code': apk.version_code
|
|
},
|
|
'released': _('Released %(date)s') % {
|
|
'date': formats.date_format(apk.added_date, 'DATE_FORMAT'),
|
|
}
|
|
}
|
|
apks.append(apk_dict)
|
|
json_response = {
|
|
'repo': self.get_repo().id,
|
|
'app': app.id,
|
|
'apks': apks
|
|
}
|
|
return JsonResponse(json_response, safe=False)
|
|
return super().post(request, args, kwargs)
|
|
if 'HTTP_RM_BACKGROUND_TYPE' in request.META:
|
|
if request.META['HTTP_RM_BACKGROUND_TYPE'] == 'screenshots':
|
|
try:
|
|
screenshots = self.add_screenshots()
|
|
except Exception as e:
|
|
logging.error(e)
|
|
return HttpResponseServerError(e)
|
|
self.get_repo().update_async() # schedule repository update
|
|
json_response = {
|
|
'repo': self.get_repo().id,
|
|
'app': self.get_object().id,
|
|
'screenshots': screenshots
|
|
}
|
|
return JsonResponse(json_response, safe=False)
|
|
if request.META['HTTP_RM_BACKGROUND_TYPE'] == 'feature-graphic':
|
|
try:
|
|
graphic = self.request.FILES.getlist('feature-graphic')[0]
|
|
self.object = self.get_object()
|
|
if self.object.feature_graphic and os.path.exists(
|
|
self.object.feature_graphic.path):
|
|
os.remove(self.object.feature_graphic.path)
|
|
self.object.feature_graphic = graphic
|
|
self.object.save()
|
|
except Exception as e:
|
|
logging.error(e)
|
|
return HttpResponseServerError(e)
|
|
self.get_repo().update_async() # schedule repository update
|
|
json_response = {
|
|
'repo': self.get_repo().id,
|
|
'app': self.get_object().id,
|
|
'feature-graphic': self.object.feature_graphic.url
|
|
}
|
|
return JsonResponse(json_response, safe=False)
|
|
return super().post(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
if 'disable-app-tracking' in form.data:
|
|
app = self.get_object()
|
|
app.tracked_remote = None
|
|
app.save()
|
|
return HttpResponseRedirect(app.get_edit_url())
|
|
else:
|
|
result = super().form_valid(form) # this saves the App
|
|
self.add_screenshots()
|
|
form.instance.repo.update_async() # schedule repository update
|
|
return result
|
|
|
|
def add_screenshots(self):
|
|
"""
|
|
Adds screenshots to an app.
|
|
:return: Array with information about screenshots
|
|
"""
|
|
screenshots = []
|
|
for screenshot in self.request.FILES.getlist('screenshots'):
|
|
screenshot = Screenshot.objects.create(app=self.get_object(), file=screenshot,
|
|
language_code=self.get_language())
|
|
screenshot = {
|
|
'id': screenshot.id,
|
|
'url': screenshot.file.url
|
|
}
|
|
screenshots.append(screenshot)
|
|
return screenshots
|
|
|
|
|
|
class AppTranslationCreateForm(AppForm):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(AppTranslationCreateForm, self).__init__(*args, **kwargs)
|
|
self.fields['lang'] = CharField(required=True, min_length=2,
|
|
widget=DataListTextInput(settings.LANGUAGES))
|
|
|
|
def clean(self):
|
|
lang = self.data.get('lang')
|
|
if lang and lang in modeltranslation_settings.AVAILABLE_LANGUAGES:
|
|
translation.activate(lang)
|
|
return super(AppTranslationCreateForm, self).clean()
|
|
|
|
def clean_lang(self):
|
|
lang = self.cleaned_data['lang'].lower()
|
|
if not re.match(language_code_re, lang):
|
|
self._errors['lang'] = _('This is not a valid language code.')
|
|
if lang in self.instance.get_available_languages():
|
|
self._errors['lang'] = _('This language already exists. Please choose another one!')
|
|
return lang
|
|
|
|
def save(self, commit=True):
|
|
return super(AppTranslationCreateForm, self).save(commit=commit)
|
|
|
|
|
|
class AppTranslationCreateView(AppEditView):
|
|
template_name = 'repomaker/app/translation_add.html'
|
|
|
|
def get_queryset(self):
|
|
return self.model.objects.all()
|
|
|
|
def get_form_class(self):
|
|
return AppTranslationCreateForm
|
|
|
|
def form_valid(self, form):
|
|
self.object.translate(form.cleaned_data['lang'])
|
|
translation.activate(form.cleaned_data['lang'])
|
|
return super().form_valid(form)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
# in this case, the requested language is a POST arg rather than being
|
|
# part of the URL
|
|
post_lang = request.POST.get('lang')
|
|
if post_lang and post_lang.lower() in modeltranslation_settings.AVAILABLE_LANGUAGES:
|
|
translation.activate(post_lang)
|
|
return super(AppTranslationCreateView, self).post(request, *args, **kwargs)
|
|
|
|
|
|
class AppDeleteView(RepositoryAuthorizationMixin, DeleteView):
|
|
model = App
|
|
pk_url_kwarg = 'app_id'
|
|
template_name = 'repomaker/app/delete.html'
|
|
|
|
def get_repo(self):
|
|
return self.get_object().repo
|
|
|
|
def get_success_url(self):
|
|
self.get_repo().update_async() # schedule repository update
|
|
return reverse_lazy('repo', kwargs={'repo_id': self.kwargs['repo_id']})
|
|
|
|
|
|
class AppFeatureGraphicDeleteView(RepositoryAuthorizationMixin, DeleteView):
|
|
model = App
|
|
pk_url_kwarg = 'app_id'
|
|
context_object_name = 'app'
|
|
template_name = 'repomaker/app/feature_graphic_delete.html'
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
"""
|
|
Deletes the feature graphic of the app and then
|
|
redirects to the success URL.
|
|
"""
|
|
self.get_object().feature_graphic.delete()
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
def get_repo(self):
|
|
return self.get_object().repo
|
|
|
|
def get_success_url(self):
|
|
self.get_repo().update_async()
|
|
return self.get_object().get_edit_url()
|