Compare commits

..

11 Commits

34 changed files with 949 additions and 281 deletions

View File

@ -29,6 +29,9 @@ class MySQLController(ServiceController):
# Create database and re-set permissions # Create database and re-set permissions
mysql -e 'CREATE DATABASE `%(database)s`;' || true mysql -e 'CREATE DATABASE `%(database)s`;' || true
mysql mysql -e 'DELETE FROM db WHERE db = "%(database)s";'\ mysql mysql -e 'DELETE FROM db WHERE db = "%(database)s";'\
# wait to create user
sleep 1.5
""") % context """) % context
) )
for user in database.users.all(): for user in database.users.all():

View File

@ -145,8 +145,9 @@ class Domain(models.Model):
else: else:
zone += subdomain.render_records() zone += subdomain.render_records()
###darmengo 2021-03-25 add autoconfig ###darmengo 2021-03-25 add autoconfig
# TODO: que se asigne esta ip automaticamente
if self.has_default_mx(): if self.has_default_mx():
zone += 'autoconfig.{}. 30m IN A 109.69.8.133\n'.format(self.name) zone += 'autoconfig.{}. 30m IN A 45.150.186.197\n'.format(self.name)
###END darmengo 2021-03-25 add autoconfig ###END darmengo 2021-03-25 add autoconfig
for subdomain in sorted(tail, key=lambda x: len(x.name), reverse=True): for subdomain in sorted(tail, key=lambda x: len(x.name), reverse=True):
zone += subdomain.render_records() zone += subdomain.render_records()

View File

@ -2,8 +2,6 @@ from django import forms
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
from orchestra.forms.widgets import DynamicHelpTextSelect
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
@ -11,12 +9,8 @@ from orchestra.contrib.domains.models import Domain, Record
from orchestra.contrib.mailboxes.models import Address, Mailbox from orchestra.contrib.mailboxes.models import Address, Mailbox
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
from orchestra.contrib.musician.validators import ValidateZoneMixin from orchestra.contrib.musician.validators import ValidateZoneMixin
from orchestra.contrib.webapps.models import WebApp, WebAppOption
from orchestra.contrib.webapps.options import AppOption
from orchestra.contrib.webapps.types import AppType
from . import api from . import api
from .settings import MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
@ -208,63 +202,3 @@ class SystemUsersChangePasswordForm(ChangePasswordForm):
fields = ("password",) fields = ("password",)
model = SystemUser model = SystemUser
class WebappOptionForm(forms.ModelForm):
OPTIONS_HELP_TEXT = {
op.name: force_str(op.help_text) for op in AppOption.get_plugins()
}
class Meta:
model = WebAppOption
fields = ("name", "value")
def __init__(self, *args, **kwargs):
try:
self.webapp = kwargs.pop('webapp')
super().__init__(*args, **kwargs)
except:
super().__init__(*args, **kwargs)
self.webapp = self.instance.webapp
target = 'this.id.replace("name", "value")'
self.fields['name'].widget.attrs = DynamicHelpTextSelect(target, self.OPTIONS_HELP_TEXT).attrs
def save(self, commit=True):
instance = super().save(commit=False)
instance.webapp = self.webapp
if commit:
super().save(commit=True)
self.webapp.save()
return instance
class WebappOptionCreateForm(WebappOptionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
plugin = AppType.get(self.webapp.type)
choices = list(plugin.get_group_options_choices())
for grupo, opciones in enumerate(choices):
if isinstance(opciones[1], list):
nueva_lista = [opc for opc in opciones[1] if opc[0] in MUSICIAN_EDIT_ENABLE_PHP_OPTIONS]
choices[grupo] = (opciones[0], nueva_lista)
self.fields['name'].widget.choices = choices
def clean(self):
cleaned_data = super().clean()
name = self.cleaned_data.get("name")
if WebAppOption.objects.filter(webapp=self.webapp, name=name).exists():
raise ValidationError(_("This option already exist."))
return cleaned_data
class WebappOptionUpdateForm(WebappOptionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.choices = [(self.initial['name'], self.initial['name'])]

View File

@ -0,0 +1,38 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from orchestra.contrib.lists.models import List
from orchestra.contrib.domains.models import Domain
class MailingUpdateForm(forms.ModelForm):
class Meta:
model = List
fields = ("is_active", "name", "address_name", "address_domain")
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
qs = Domain.objects.filter(account=self.user)
self.fields['address_domain'].queryset = qs
self.fields['address_name'].help_text = _("Additional address besides the default <name>@grups.pangea.org")
self.fields['name'].widget.attrs['readonly'] = True
class MailingCreateForm(forms.ModelForm):
class Meta:
model = List
fields = ("name", "address_name", "address_domain", "admin_email")
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
qs = Domain.objects.filter(account=self.user)
self.fields['address_domain'].queryset = qs
self.fields['address_name'].help_text = _("Additional address besides the default <name>@grups.pangea.org")
def save(self, commit=True):
instance = super().save(commit=False)
instance.account = self.user
if commit:
super().save(commit=True)
return instance

View File

@ -0,0 +1,80 @@
from django.utils.translation import gettext_lazy as _
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.views.generic.list import ListView
from orchestra.contrib.musician.mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin)
from django.views.generic.edit import (CreateView, DeleteView, FormView,
UpdateView)
from orchestra.contrib.lists.models import List
from orchestra.contrib.domains.models import Domain, Record
from orchestra.contrib.lists.settings import LISTS_DEFAULT_DOMAIN
from .forms import MailingUpdateForm, MailingCreateForm
class MailingListsView(CustomContextMixin, UserTokenRequiredMixin, ListView):
model = List
template_name = "musician/mailinglist_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Mailing lists'),
}
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
domain_id = self.request.GET.get('domain')
if domain_id:
qs = Domain.objects.filter(account=self.request.user)
context.update({
'active_domain': get_object_or_404(qs, pk=domain_id),
})
context.update({'default_domain': LISTS_DEFAULT_DOMAIN})
return context
def get_queryfilter(self):
"""Retrieve query params (if any) to filter queryset"""
domain_id = self.request.GET.get('domain')
if domain_id:
return {"address_domain_id": domain_id}
return {}
class MailingUpdateView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
model = List
form_class = MailingUpdateForm
template_name = "musician/mailinglist_form.html"
def get_queryset(self):
qs = List.objects.filter(account=self.request.user)
return qs
def get_success_url(self):
return reverse_lazy("musician:mailing-lists")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
class MailingCreateView(CustomContextMixin, UserTokenRequiredMixin, CreateView):
model = List
form_class = MailingCreateForm
template_name = "musician/mailinglist_form.html"
success_url = reverse_lazy("musician:mailing-lists")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
class MailingDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
template_name = "musician/mailing_check_delete.html"
model = List
success_url = reverse_lazy("musician:mailing-lists")
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)

View File

@ -58,3 +58,9 @@ MUSICIAN_EDIT_ENABLE_PHP_OPTIONS = Setting('MUSICIAN_EDIT_ENABLE_PHP_OPTIONS', (
'post_max_size', 'post_max_size',
'upload_max_filesize', 'upload_max_filesize',
)) ))
MUSICIAN_WEBSITES_ENABLE_GROUP_DIRECTIVE = Setting('MUSICIAN_WEBSITES_ENABLE_GROUP_DIRECTIVE', (
'HTTPD',
),
help_text="Valid groups: HTTPD, ModSecurity, SSL, SaaS"
)

View File

@ -0,0 +1,12 @@
{% extends "musician/base.html" %}
{% load i18n %}
{% block content %}
<form method="post">
{% csrf_token %}
<p>{% blocktrans %}Are you sure that you want remove the list: "{{ list }}"?{% endblocktrans %}</p>
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
<a class="btn btn-light mr-2" href="{% url 'musician:mailing-lists' %}">{% trans 'Cancel' %}</a>
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
</form>
{% endblock %}

View File

@ -0,0 +1,40 @@
{% extends "musician/base.html" %}
{% load bootstrap4 i18n %}
{% block content %}
<style>
.form-check{
background-color: #fff;
padding: .375rem 2.0rem;
border: 1px solid #ced4da;
border-radius: 5px;
}
</style>
<a class="btn-arrow-left" href="{% url 'musician:mailing-lists' %}">{% trans "Go back" %}</a>
<h1 class="service-name">
{% if form.instance.pk %}
{% trans "Update list" %} <span class="font-weight-light">{{ list.name }}</span>
{% else %}
{% trans "Create list" %}
{% endif %}
</h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-light mr-2" href="{% url 'musician:mailing-lists' %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% if form.instance.pk %}
<div class="float-right">
<a class="btn btn-danger" href="{% url 'musician:mailing-delete' view.kwargs.pk %}">{% trans "Delete" %}</a>
</div>
{% endif %}
{% endbuttons %}
</form>
{% endblock %}

View File

@ -13,9 +13,10 @@
<colgroup> <colgroup>
<col span="1" style="width: 13%;"> <col span="1" style="width: 13%;">
<col span="1" style="width: 12%;"> <col span="1" style="width: 12%;">
<col span="1" style="width: 50%;"> <col span="1" style="width: 40%;">
<col span="1" style="width: 15%;"> <col span="1" style="width: 15%;">
<col span="1" style="width: 10%;"> <col span="1" style="width: 10%;">
<col span="1" style="width: 10%;">
</colgroup> </colgroup>
<thead class="thead-dark"> <thead class="thead-dark">
<tr> <tr>
@ -24,6 +25,7 @@
<th scope="col">Address</th> <th scope="col">Address</th>
<th scope="col">Admin email</th> <th scope="col">Admin email</th>
<th scope="col">Configure</th> <th scope="col">Configure</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -35,12 +37,25 @@
{% else %} {% else %}
<td class="text-danger font-weight-bold">{% trans "Inactive" %}</td> <td class="text-danger font-weight-bold">{% trans "Inactive" %}</td>
{% endif %} {% endif %}
<td>{{ resource.address_name}}</td> <td>
{% if resource.address %}
{{ resource.address }}
{% else %}
{{ resource.name }}@{{ default_domain }}
{% endif %}
</td>
<td>{{ resource.admin_email }}</td> <td>{{ resource.admin_email }}</td>
<td><a href="{{ resource.get_absolute_url }}" target="_blank" rel="noopener noreferrer">Mailman <i class="fas fa-external-link-alt"></i></a></td> <td><a href="{{ resource.get_absolute_url }}" target="_blank" rel="noopener noreferrer">Mailman <i class="fas fa-external-link-alt"></i></a></td>
<td class="text-right">
<a class="btn btn-outline-warning" href="{% url 'musician:mailing-update' resource.id %}">
<i class="fas fa-tools"></i></a>
<a href="{% url 'musician:mailing-delete' resource.id %}">
<i class="ml-3 text-danger fas fa-trash"></i></a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
{% include "musician/components/table_paginator.html" %}
</table> </table>
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:mailing-create' %}">{% trans "New list" %}</a>
{% endblock %} {% endblock %}

View File

@ -2,7 +2,7 @@
{% load bootstrap4 i18n %} {% load bootstrap4 i18n %}
{% block content %} {% block content %}
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1> <h1 class="service-name">{% trans "Change password for" %}: <span class="font-weight-light">{{ object.username }}</span></h1>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,46 +0,0 @@
{% extends "musician/base.html" %}
{% load i18n %}
{% block content %}
<a class="btn-arrow-left" href="{% url 'musician:webapp-list' %}">{% trans "Go back" %}</a>
<h1 class="service-name">
{% trans "PHP settings for" %} <span class="font-weight-light">{{ object.name }}</span>
</h1>
<p class="service-description">{% trans "PHP settings page description." %}</p>
<table class="table service-list">
<colgroup>
<col span="1" style="width: 12%;">
<col span="1" style="width: 10%;">
<col span="1" style="width: 78%;">
</colgroup>
<thead class="thead-dark">
<tr>
<th scope="col">{% trans "Type" %}</th>
<th scope="col">{% trans "Value" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for option in object.options.all %}
<tr>
<td>{{ option.name }}</td>
<td>{{ option.value }}</td>
<td class="text-right">
{% if option.name in edit_allowed_PHP_options %}
<a href="{% url 'musician:webapp-update-option' object.pk option.pk %}">
<i class="ml-3 fas fa-edit"></i></a>
{% endif %}
<a href="{% url 'musician:webapp-delete-option' object.pk option.pk %}">
<i class="ml-3 text-danger fas fa-trash"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:webapp-add-option' object.pk %}">{% trans "Add new option" %}</a></td>
{% endblock %}

View File

@ -0,0 +1,121 @@
{% extends "musician/base.html" %}
{% load i18n %}
{% block content %}
<a class="btn-arrow-left" href="{% url 'musician:webapp-list' %}">{% trans "Go back" %}</a>
<h1 class="service-name">{% trans "WebApp Options for" %} {{ object.name }}</h1>
<hr>
<table class="table service-list">
<tbody>
<tr>
<th scope="row">Server:</th>
<td colspan="2">{{ object.target_server }}</td>
</tr>
<tr>
<th scope="row">Type:</th>
<td colspan="2">{{ object.type }}</td>
</tr>
{% if object.data.php_version %}
<tr>
<th scope="row">PHP:</th>
<td colspan="2">{{ object.data.php_version }}</td>
</tr>
{% endif %}
<tr>
<th scope="row">SFTP user:</th>
{% if object.sftpuser %}
<td>{{ object.sftpuser }}</td>
<td>
<div class="d-flex justify-content-end">
<a class="btn btn-outline-warning" href="{% url 'musician:webappuser-password' object.sftpuser.id %}">
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
</div>
</td>
{% else %}
<td>{{ object.account.main_systemuser }}</td>
<td>
<div class="d-flex justify-content-end">
<a class="btn btn-outline-warning" href="{% url 'musician:systemuser-password' object.account.main_systemuser.id %}">
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
</div>
</td>
{% endif %}
</tr>
<tr>
<th scope="row">Path:</th>
<td colspan="2">{{ object.get_base_path }}</td>
</tr>
</tbody>
</table>
<!-- Si es una aplicacion con bbdd tipo WP/moodle/lime mostrar bbdd -->
{% if object.data.db_name %}
<table class="table service-list">
<tbody>
<tr>
<th scope="row">{% trans "Database:" %} </th>
<td>{{ object.data.db_name }}</td>
</tr>
<tr>
<th scope="row">{% trans "User Database:" %}</th>
<td> {{ object.data.db_user }}</td>
</tr>
<tr>
<th scope="row">{% trans "Password:" %} </th>
<td>{{ object.data.password }}</td>
</tr>
</tbody>
</table>
<div class="alert alert-warning" role="alert">
Initial database and App admin password. <br>
Subsequent changes to the admin password will not be reflected.
</div>
{% endif %}
<!-- Opciones PHP -->
<h3 class="service-name">{% trans "PHP settings" %}</h3>
<hr>
{% if object.options.all|length != 0 %}
<p class="service-description">{% trans "PHP settings page description." %}</p>
<table class="table service-list">
<colgroup>
<col span="1" style="width: 12%;">
<col span="1" style="width: 10%;">
<col span="1" style="width: 78%;">
</colgroup>
<thead class="thead-dark">
<tr>
<th scope="col">{% trans "Type" %}</th>
<th scope="col">{% trans "Value" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for option in object.options.all %}
<tr>
<td>{{ option.name }}</td>
<td>{{ option.value }}</td>
<td class="text-right">
{% if option.name in edit_allowed_PHP_options %}
<a href="{% url 'musician:webapp-update-option' object.pk option.pk %}">
<i class="ml-3 fas fa-edit"></i></a>
{% endif %}
<a href="{% url 'musician:webapp-delete-option' object.pk option.pk %}">
<i class="ml-3 text-danger fas fa-trash"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans "This WebApp has PHP options by default, create one if you need it." %}</p>
{% endif %}
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:webapp-add-option' object.pk %}">{% trans "Add new option" %}</a></td>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% load bootstrap4 i18n %} {% load bootstrap4 i18n %}
{% block content %} {% block content %}
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1> <h1 class="service-name">{% trans "Change password for" %}: <span class="font-weight-light">{{ object.username }}</span></h1>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -31,7 +31,6 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
{% include "musician/components/table_paginator.html" %}
</table> </table>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "musician/base.html" %}
{% load bootstrap4 i18n %}
{% block content %}
<a class="btn-arrow-left" href="{% url 'musician:website-detail' view.kwargs.pk %}">{% trans "Go back" %}</a>
<h1 class="service-name">
{% trans "Add Option to" %} <span class="font-weight-light">{{ form.website.name }}</span>
</h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-light mr-2" href="{% url 'musician:website-detail' view.kwargs.pk %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% endbuttons %}
</form>
{% endblock %}

View File

@ -0,0 +1,97 @@
{% extends "musician/base.html" %}
{% load i18n %}
{% block content %}
<a class="btn-arrow-left" href="{% url 'musician:website-list' %}">{% trans "Go back" %}</a>
<h1 class="service-name">{% trans "WebSite Options for" %} {{ object.name }}</h1>
<hr>
<table class="table service-list">
<tbody>
<tr>
<th scope="row">Server:</th>
<td>{{ object.target_server }}</td>
</tr>
<tr>
<th scope="row">Domains:</th>
<td>
{% for domain in object.domains.all %}
{{ domain }} <br>
{% endfor %}
</td>
</tr>
<tr>
<th scope="row">active:</th>
<td class="text-{{website.is_active|yesno:'success,danger'}}">
<i class="fa fa-{{ website.is_active|yesno:'check,times' }}"></i>
<span class="sr-only">{{ website.is_active|yesno }}</span>
</td>
</tr>
<tr>
<th scope="row">Protocol:</th>
<td>{{ object.protocol }}</td>
</tr>
</tbody>
</table>
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:website-update' object.pk %}">{% trans "Edit options" %}</a></td>
<!-- Contents -->
<h3 class="service-name">{% trans "Contents" %}</h3>
<hr>
<p>{% trans "Webapps assigned to this website" %}</p>
<table class="table service-list">
<thead class="thead-dark">
<tr>
<th scope="col">{% trans "Webapp" %}</th>
<th scope="col">{% trans "Type " %}</th>
<th scope="col">{% trans "Path" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for option in content %}
<tr>
<td>{{ option.webapp }}</td>
<td>{{ option.webapp.get_type_display }}</td>
<td>{{ option.path }}</td>
<td class="text-right">
<a href="{% url 'musician:website-delete-content' object.pk option.pk %}">
<i class="ml-3 text-danger fas fa-trash"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:website-add-content' object.pk %}">{% trans "Assigned new Webapp" %}</a></td>
<!-- Directives -->
<h3 class="service-name">{% trans "Directives" %}</h3>
<hr>
<p>{% trans "Options assigned to this Website" %} </p>
<table class="table service-list">
<thead class="thead-dark">
<tr>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Value" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for option in directives %}
<tr>
<td>{{ option.name }}</td>
<td>{{ option.value }}</td>
<td class="text-right">
<a href="{% url 'musician:website-delete-directive' object.pk option.pk %}">
<i class="ml-3 text-danger fas fa-trash"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:website-add-directive' object.pk %}">{% trans "Add new directive" %}</a></td>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "musician/base.html" %}
{% load bootstrap4 i18n %}
{% block content %}
<style>
.form-check{
background-color: #fff;
padding: .375rem 2.0rem;
border: 1px solid #ced4da;
border-radius: 5px;
}
</style>
<a class="btn-arrow-left" href="{% url 'musician:website-detail' view.kwargs.pk %}">{% trans "Go back" %}</a>
<h1 class="service-name">
{% trans "Update Option of Website" %} <span class="font-weight-light">{{ website.name }}</span>
</h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-light mr-2" href="{% url 'musician:website-detail' view.kwargs.pk %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% endbuttons %}
</form>
{% endblock %}

View File

@ -38,11 +38,8 @@
<span class="sr-only">{{ website.is_active|yesno }}</span> <span class="sr-only">{{ website.is_active|yesno }}</span>
</td> </td>
<td> <td>
<!-- <a class="btn btn-outline-warning" href="#"> <a class="btn btn-outline-warning" href="{% url 'musician:website-detail' website.id %}">
<i class="fas fa-tools"></i></a> --> <i class="fas fa-tools"></i></a>
<button type="button" class="btn btn-outline-warning" data-toggle="modal" data-target="#exampleModal">
<i class="fas fa-tools"></i>
</button>
</td> </td>
</tr> </tr>
<!-- Fila oculta de webapp --> <!-- Fila oculta de webapp -->
@ -55,7 +52,7 @@
<td>Webapp Dir</td> <td>Webapp Dir</td>
<td>/home/{{ content.webapp.account }}/webapps/{{ content.webapp }}</td> <td>/home/{{ content.webapp.account }}/webapps/{{ content.webapp }}</td>
<td class="text-right"> <td class="text-right">
<a class="btn btn-outline-secondary" href="{% url 'musician:webapp-list'%}"> <a class="btn btn-outline-secondary" href="{% url 'musician:webapp-detail' content.webapp.id %}">
<i class="fas fa-tools"></i></a> <i class="fas fa-tools"></i></a>
</td> </td>
</tr> </tr>
@ -107,22 +104,4 @@
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Sorry!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{% trans "This section is under development." %}
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "musician/base.html" %}
{% load i18n %}
{% block content %}
<form method="post">
{% csrf_token %}
<p>{% blocktrans %}Are you sure that you want remove the following option"?{% endblocktrans %}</p>
{% if object.name %}
<pre>{{ object.name }} {{ object.value }}</pre>
{% else %}
<pre>Webapp {{ object.webapp }} assigned in website {{ object.website }}</pre>
{% endif %}
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
<a class="btn btn-secondary" href="{% url 'musician:website-detail' view.kwargs.pk %}">{% trans 'Cancel' %}</a>
</form>
{% endblock %}

View File

@ -25,26 +25,42 @@ urlpatterns = [
path('billing/', views.BillingView.as_view(), name='billing'), path('billing/', views.BillingView.as_view(), name='billing'),
path('bills/<int:pk>/download/', views.BillDownloadView.as_view(), name='bill-download'), path('bills/<int:pk>/download/', views.BillDownloadView.as_view(), name='bill-download'),
path('profile/', views.ProfileView.as_view(), name='profile'), path('profile/', views.ProfileView.as_view(), name='profile'),
path('profile/setLang/<code>', views.profile_set_language, name='profile-set-lang'), path('profile/setLang/<code>', views.profile_set_language, name='profile-set-lang'),
path('address/', views.AddressListView.as_view(), name='address-list'), path('address/', views.AddressListView.as_view(), name='address-list'),
path('address/new/', views.MailCreateView.as_view(), name='address-create'), path('address/new/', views.MailCreateView.as_view(), name='address-create'),
path('address/<int:pk>/', views.MailUpdateView.as_view(), name='address-update'), path('address/<int:pk>/', views.MailUpdateView.as_view(), name='address-update'),
path('address/<int:pk>/delete/', views.AddressDeleteView.as_view(), name='address-delete'), path('address/<int:pk>/delete/', views.AddressDeleteView.as_view(), name='address-delete'),
path('mailboxes/', views.MailboxListView.as_view(), name='mailbox-list'), path('mailboxes/', views.MailboxListView.as_view(), name='mailbox-list'),
path('mailboxes/new/', views.MailboxCreateView.as_view(), name='mailbox-create'), path('mailboxes/new/', views.MailboxCreateView.as_view(), name='mailbox-create'),
path('mailboxes/<int:pk>/', views.MailboxUpdateView.as_view(), name='mailbox-update'), path('mailboxes/<int:pk>/', views.MailboxUpdateView.as_view(), name='mailbox-update'),
path('mailboxes/<int:pk>/delete/', views.MailboxDeleteView.as_view(), name='mailbox-delete'), path('mailboxes/<int:pk>/delete/', views.MailboxDeleteView.as_view(), name='mailbox-delete'),
path('mailboxes/<int:pk>/change-password/', views.MailboxChangePasswordView.as_view(), name='mailbox-password'), path('mailboxes/<int:pk>/change-password/', views.MailboxChangePasswordView.as_view(), name='mailbox-password'),
path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'), path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'),
path('mailing-lists/<int:pk>/', views.MailingUpdateView.as_view(), name='mailing-update'),
path('mailing-lists/new/', views.MailingCreateView.as_view(), name='mailing-create'),
path('mailing-lists/<int:pk>/delete/', views.MailingDeleteView.as_view(), name='mailing-delete'),
path('databases/', views.DatabaseListView.as_view(), name='database-list'), path('databases/', views.DatabaseListView.as_view(), name='database-list'),
path('saas/', views.SaasListView.as_view(), name='saas-list'), path('saas/', views.SaasListView.as_view(), name='saas-list'),
path('webappusers/', views.WebappUserListView.as_view(), name='webappuser-list'), path('webappusers/', views.WebappUserListView.as_view(), name='webappuser-list'),
path('webappuser/<int:pk>/change-password/', views.WebappUserChangePasswordView.as_view(), name='webappuser-password'), path('webappuser/<int:pk>/change-password/', views.WebappUserChangePasswordView.as_view(), name='webappuser-password'),
path('systemusers/', views.SystemUserListView.as_view(), name='systemuser-list'), path('systemusers/', views.SystemUserListView.as_view(), name='systemuser-list'),
path('systemuser/<int:pk>/change-password/', views.SystemUserChangePasswordView.as_view(), name='systemuser-password'), path('systemuser/<int:pk>/change-password/', views.SystemUserChangePasswordView.as_view(), name='systemuser-password'),
path('websites/', views.WebsiteListView.as_view(), name='website-list'), path('websites/', views.WebsiteListView.as_view(), name='website-list'),
path('websites/<int:pk>/', views.WebsiteDetailView.as_view(), name='website-detail'),
path('websites/<int:pk>/edit/', views.WebsiteUpdateView.as_view(), name='website-update'),
path('websites/<int:pk>/add-content/', views.WebsiteAddContentView.as_view(), name='website-add-content'),
path('websites/<int:pk>/add-directive/', views.WebsiteAddDirectiveView.as_view(), name='website-add-directive'),
path('websites/<int:pk>/content/<int:content_pk>/delete/', views.WebsiteDeleteContentView.as_view(), name='website-delete-content'),
path('websites/<int:pk>/directive/<int:directive_pk>/delete/', views.WebsiteDeleteDirectiveView.as_view(), name='website-delete-directive'),
path('webapps/', views.WebappListView.as_view(), name='webapp-list'), path('webapps/', views.WebappListView.as_view(), name='webapp-list'),
path('webapps/<int:pk>/', views.WebappDetailView.as_view(), name='webapp-detail'), path('webapps/<int:pk>/', views.WebappDetailView.as_view(), name='webapp-detail'),

View File

@ -33,15 +33,13 @@ from orchestra.contrib.mailboxes.models import Address, Mailbox
from orchestra.contrib.resources.models import Resource, ResourceData from orchestra.contrib.resources.models import Resource, ResourceData
from orchestra.contrib.saas.models import SaaS from orchestra.contrib.saas.models import SaaS
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
from orchestra.contrib.websites.models import Website
from orchestra.contrib.webapps.models import WebApp, WebAppOption
from orchestra.utils.html import html_to_pdf from orchestra.utils.html import html_to_pdf
from .auth import logout as auth_logout from .auth import logout as auth_logout
from .forms import (LoginForm, MailboxChangePasswordForm, MailboxCreateForm, from .forms import (LoginForm, MailboxChangePasswordForm, MailboxCreateForm,
MailboxSearchForm, MailboxUpdateForm, MailForm, MailboxSearchForm, MailboxUpdateForm, MailForm,
RecordCreateForm, RecordUpdateForm, WebappUsersChangePasswordForm, RecordCreateForm, RecordUpdateForm, WebappUsersChangePasswordForm,
SystemUsersChangePasswordForm, WebappOptionCreateForm, WebappOptionUpdateForm) SystemUsersChangePasswordForm)
from .mixins import (CustomContextMixin, ExtendedPaginationMixin, from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin) UserTokenRequiredMixin)
from .models import Address as AddressService from .models import Address as AddressService
@ -52,6 +50,10 @@ from .models import MailinglistService, SaasService
from .settings import ALLOWED_RESOURCES, MUSICIAN_EDIT_ENABLE_PHP_OPTIONS from .settings import ALLOWED_RESOURCES, MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
from .utils import get_bootstraped_percent from .utils import get_bootstraped_percent
from .webapps.views import *
from .websites.views import *
from .lists.views import *
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -314,37 +316,6 @@ class AddressDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
return self.model.objects.filter(account=self.request.user) return self.model.objects.filter(account=self.request.user)
class MailingListsView(ServiceListView):
service_class = MailinglistService
model = List
template_name = "musician/mailinglist_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Mailing lists'),
}
def get_queryset(self):
return self.model.objects.filter(account=self.request.user).order_by("name")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
domain_id = self.request.GET.get('domain')
if domain_id:
qs = Domain.objects.filter(account=self.request.user)
context.update({
'active_domain': get_object_or_404(qs, pk=domain_id)
})
return context
def get_queryfilter(self):
"""Retrieve query params (if any) to filter queryset"""
domain_id = self.request.GET.get('domain')
if domain_id:
return {"address_domain_id": domain_id}
return {}
class MailboxListView(ServiceListView): class MailboxListView(ServiceListView):
service_class = MailboxService service_class = MailboxService
model = Mailbox model = Mailbox
@ -620,14 +591,14 @@ class LogoutView(RedirectView):
class WebappUserListView(ServiceListView): class WebappUserListView(ServiceListView):
model = WebappUsers model = WebappUsers
template_name = "musician/webappuser_list.html" template_name = "musician/webapps/webappuser_list.html"
extra_context = { extra_context = {
# Translators: This message appears on the page title # Translators: This message appears on the page title
'title': _('Webapp users'), 'title': _('Webapp users'),
} }
class WebappUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, UpdateView): class WebappUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
template_name = "musician/webappuser_change_password.html" template_name = "musician/webapps/webappuser_change_password.html"
model = WebappUsers model = WebappUsers
form_class = WebappUsersChangePasswordForm form_class = WebappUsersChangePasswordForm
success_url = reverse_lazy("musician:webappuser-list") success_url = reverse_lazy("musician:webappuser-list")
@ -653,103 +624,3 @@ class SystemUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, U
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(account=self.request.user) return self.model.objects.filter(account=self.request.user)
class WebsiteListView(CustomContextMixin, UserTokenRequiredMixin, ListView):
model = Website
template_name = "musician/website_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Websites'),
}
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'description': _("A website is the place where a domain is associated with the directory where the web files are located. (WebApp)"),
})
return context
class WebappListView(CustomContextMixin, UserTokenRequiredMixin, ListView):
model = WebApp
template_name = "musician/webapp_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Webapps'),
}
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'description': _("A web app is the directory where your website is stored. Through SFTP, you can access this directory and upload/edit/delete files."),
'description2': _("Each Webapp has its own SFTP user, which is created automatically when the Webapp is created.")
})
return context
class WebappDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView):
template_name = "musician/webapp_detail.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('webapp details'),
}
def get_queryset(self):
return WebApp.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'edit_allowed_PHP_options': MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
})
return context
class WebappAddOptionView(CustomContextMixin, UserTokenRequiredMixin, CreateView):
model = WebAppOption
form_class = WebappOptionCreateForm
template_name = "musician/webapp_option_form.html"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
webapp = get_object_or_404(WebApp, account=self.request.user, pk=self.kwargs["pk"])
kwargs['webapp'] = webapp
return kwargs
def get_success_url(self):
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
class WebappDeleteOptionView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
model = WebAppOption
template_name = "musician/webappoption_check_delete.html"
pk_url_kwarg = "option_pk"
def get_queryset(self):
qs = WebAppOption.objects.filter(webapp__account=self.request.user, webapp=self.kwargs["pk"])
return qs
def get_success_url(self):
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
def delete(self, request, *args, **kwargs):
object = self.get_object()
response = super().delete(request, *args, **kwargs)
object.webapp.save()
return response
class WebappUpdateOptionView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
model = WebAppOption
form_class = WebappOptionUpdateForm
template_name = "musician/webapp_option_form.html"
pk_url_kwarg = "option_pk"
def get_queryset(self):
qs = WebAppOption.objects.filter(webapp__account=self.request.user, webapp=self.kwargs["pk"])
return qs
def get_success_url(self):
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})

View File

@ -0,0 +1,69 @@
from django import forms
from orchestra.forms.widgets import DynamicHelpTextSelect
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
from orchestra.contrib.webapps.models import WebApp, WebAppOption
from orchestra.contrib.webapps.options import AppOption
from orchestra.contrib.webapps.types import AppType
from orchestra.contrib.musician.settings import MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
class WebappOptionForm(forms.ModelForm):
OPTIONS_HELP_TEXT = {
op.name: force_str(op.help_text) for op in AppOption.get_plugins()
}
class Meta:
model = WebAppOption
fields = ("name", "value")
def __init__(self, *args, **kwargs):
try:
self.webapp = kwargs.pop('webapp')
super().__init__(*args, **kwargs)
except:
super().__init__(*args, **kwargs)
self.webapp = self.instance.webapp
target = 'this.id.replace("name", "value")'
self.fields['name'].widget.attrs = DynamicHelpTextSelect(target, self.OPTIONS_HELP_TEXT).attrs
def save(self, commit=True):
instance = super().save(commit=False)
instance.webapp = self.webapp
if commit:
super().save(commit=True)
self.webapp.save()
return instance
class WebappOptionCreateForm(WebappOptionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
plugin = AppType.get(self.webapp.type)
choices = list(plugin.get_group_options_choices())
for grupo, opciones in enumerate(choices):
if isinstance(opciones[1], list):
nueva_lista = [opc for opc in opciones[1] if opc[0] in MUSICIAN_EDIT_ENABLE_PHP_OPTIONS]
choices[grupo] = (opciones[0], nueva_lista)
self.fields['name'].widget.choices = choices
def clean(self):
cleaned_data = super().clean()
name = self.cleaned_data.get("name")
if WebAppOption.objects.filter(webapp=self.webapp, name=name).exists():
raise ValidationError(_("This option already exist."))
return cleaned_data
class WebappOptionUpdateForm(WebappOptionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.choices = [(self.initial['name'], self.initial['name'])]

View File

@ -0,0 +1,102 @@
from django.utils.translation import gettext_lazy as _
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import ListView
from orchestra.contrib.musician.mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin)
from orchestra.contrib.webapps.models import WebApp, WebAppOption
from .forms import WebappOptionCreateForm, WebappOptionUpdateForm
from orchestra.contrib.musician.settings import MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
class WebappListView(CustomContextMixin, UserTokenRequiredMixin, ListView):
model = WebApp
template_name = "musician/webapps/webapp_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Webapps'),
}
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'description': _("A web app is the directory where your website is stored. Through SFTP, you can access this directory and upload/edit/delete files."),
'description2': _("Each Webapp has its own SFTP user, which is created automatically when the Webapp is created.")
})
return context
class WebappDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView):
template_name = "musician/webapps/webapp_detail.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('webapp details'),
}
def get_queryset(self):
return WebApp.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'edit_allowed_PHP_options': MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
})
return context
class WebappAddOptionView(CustomContextMixin, UserTokenRequiredMixin, CreateView):
model = WebAppOption
form_class = WebappOptionCreateForm
template_name = "musician/webapps/webapp_option_form.html"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
webapp = get_object_or_404(WebApp, account=self.request.user, pk=self.kwargs["pk"])
kwargs['webapp'] = webapp
return kwargs
def get_success_url(self):
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
class WebappDeleteOptionView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
model = WebAppOption
template_name = "musician/webapps/webappoption_check_delete.html"
pk_url_kwarg = "option_pk"
def get_queryset(self):
qs = WebAppOption.objects.filter(webapp__account=self.request.user, webapp=self.kwargs["pk"])
return qs
def get_success_url(self):
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
def delete(self, request, *args, **kwargs):
object = self.get_object()
response = super().delete(request, *args, **kwargs)
object.webapp.save()
return response
class WebappUpdateOptionView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
model = WebAppOption
form_class = WebappOptionUpdateForm
template_name = "musician/webapps/webapp_option_form.html"
pk_url_kwarg = "option_pk"
def get_queryset(self):
qs = WebAppOption.objects.filter(webapp__account=self.request.user, webapp=self.kwargs["pk"])
return qs
def get_success_url(self):
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})

View File

@ -0,0 +1,121 @@
from django import forms
from orchestra.forms.widgets import DynamicHelpTextSelect
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
from orchestra.contrib.websites.directives import SiteDirective
from orchestra.contrib.websites.models import Website, Content, WebsiteDirective
from orchestra.contrib.webapps.models import WebApp
from orchestra.contrib.domains.models import Domain
from orchestra.contrib.musician.settings import MUSICIAN_WEBSITES_ENABLE_GROUP_DIRECTIVE
class WebsiteUpdateForm(forms.ModelForm):
class Meta:
model = Website
fields = ("is_active", "protocol", "domains")
help_texts = {
'domains': _('Hold down "Control", or "Command" on a Mac, to select more than one.')
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
# Excluir dominios de otros websites
qs = Website.objects.filter(account=self.user).exclude(id=self.instance.id)
used_domains = []
for website in qs:
dominios = website.domains.all()
for dominio in dominios:
used_domains.append(dominio)
self.fields['domains'].queryset = Domain.objects.filter(account=self.user).exclude(name__in=used_domains)
class WesiteContentCreateForm(forms.ModelForm):
class Meta:
model = Content
fields = ("webapp", "path")
def __init__(self, *args, **kwargs):
self.website = kwargs.pop('website')
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['webapp'].queryset = WebApp.objects.filter(account=self.user, target_server=self.website.target_server)
def clean(self):
cleaned_data = super().clean()
path = self.cleaned_data.get("path")
path = "/" if path == "" else path
if Content.objects.filter(website=self.website, path=path).exists():
self.add_error('path',_("This Path already exists on this Website."))
return cleaned_data
def save(self, commit=True):
instance = super().save(commit=False)
instance.website = self.website
if commit:
super().save(commit=True)
self.website.save()
return instance
from collections import defaultdict
from orchestra.contrib.websites.utils import normurlpath
class WesiteDirectiveCreateForm(forms.ModelForm):
DIRECTIVES_HELP_TEXT = {
op.name: force_str(op.help_text) for op in SiteDirective.get_plugins()
}
class Meta:
model = WebsiteDirective
fields = ("name", "value")
def __init__(self, *args, **kwargs):
self.website = kwargs.pop('website')
# self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
target = 'this.id.replace("name", "value")'
self.fields['name'].widget.attrs = DynamicHelpTextSelect(target, self.DIRECTIVES_HELP_TEXT).attrs
self.fields['name'].choices = self.get_allow_choices()
def get_allow_choices(self):
groups = MUSICIAN_WEBSITES_ENABLE_GROUP_DIRECTIVE
yield (None, '-------')
options = SiteDirective.get_option_groups()
for grp in groups:
if grp in options.keys():
yield (grp, [(op.name, op.verbose_name) for op in options[grp]])
def clean(self):
# Recoge todos los paths de directive y contents
locations = set()
for content in Content.objects.filter(website=self.website):
location = content.path
if location is not None:
locations.add(location)
for directive_obj in WebsiteDirective.objects.filter(website=self.website):
location = directive_obj.value
if location is not None:
locations.add(normurlpath(location.split()[0]))
# Comprueva que no se repitan
directive = self.cleaned_data
value = normurlpath(directive.get('value'))
if value:
value = value.split()[0]
if value in locations:
self.add_error('value', f"Location '{value}' already in use by other content/directive.")
def save(self, commit=True):
instance = super().save(commit=False)
instance.website = self.website
if commit:
super().save(commit=True)
self.website.save()
return instance

View File

@ -0,0 +1,140 @@
from django.utils.translation import gettext_lazy as _
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.edit import (CreateView, DeleteView, FormView,
UpdateView)
from django.views.generic.list import ListView
from orchestra.contrib.musician.mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin)
from orchestra.contrib.websites.models import Website, Content, WebsiteDirective
from .forms import ( WebsiteUpdateForm, WesiteContentCreateForm,
WesiteDirectiveCreateForm)
class WebsiteListView(CustomContextMixin, UserTokenRequiredMixin, ListView):
model = Website
template_name = "musician/websites/website_list.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('Websites'),
}
def get_queryset(self):
return self.model.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'description': _("A website is the place where a domain is associated with the directory where the web files are located. (WebApp)"),
})
return context
class WebsiteDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView):
template_name = "musician/websites/website_detail.html"
extra_context = {
# Translators: This message appears on the page title
'title': _('website details'),
}
def get_queryset(self):
return Website.objects.filter(account=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content'] = Content.objects.filter(website=self.object)
context['directives'] = WebsiteDirective.objects.filter(website=self.object)
return context
class WebsiteUpdateView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
model = Website
form_class = WebsiteUpdateForm
template_name = "musician/websites/website_form.html"
def get_queryset(self):
qs = Website.objects.filter(account=self.request.user)
return qs
def get_success_url(self):
return reverse_lazy("musician:website-detail", kwargs={"pk": self.kwargs["pk"]})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
class WebsiteDeleteContentView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
model = Content
template_name = "musician/websites/websiteoption_check_delete.html"
pk_url_kwarg = "content_pk"
def get_queryset(self):
qs = Content.objects.filter(website__account=self.request.user, website=self.kwargs["pk"])
return qs
def get_success_url(self):
return reverse_lazy("musician:website-detail", kwargs={"pk": self.kwargs["pk"]})
def delete(self, request, *args, **kwargs):
object = self.get_object()
response = super().delete(request, *args, **kwargs)
object.website.save()
return response
class WebsiteDeleteDirectiveView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
model = WebsiteDirective
template_name = "musician/websites/websiteoption_check_delete.html"
pk_url_kwarg = "directive_pk"
def get_queryset(self):
qs = WebsiteDirective.objects.filter(website__account=self.request.user, website=self.kwargs["pk"])
return qs
def get_success_url(self):
return reverse_lazy("musician:website-detail", kwargs={"pk": self.kwargs["pk"]})
def delete(self, request, *args, **kwargs):
object = self.get_object()
response = super().delete(request, *args, **kwargs)
object.website.save()
return response
class WebsiteAddContentView(CustomContextMixin, UserTokenRequiredMixin, CreateView):
model = Content
form_class = WesiteContentCreateForm
template_name = "musician/websites/website_create_option_form.html"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
website = get_object_or_404(Website, account=self.request.user, pk=self.kwargs["pk"])
kwargs['website'] = website
kwargs["user"] = self.request.user
return kwargs
def get_success_url(self):
return reverse_lazy("musician:website-detail", kwargs={"pk": self.kwargs["pk"]})
class WebsiteAddDirectiveView(CustomContextMixin, UserTokenRequiredMixin, CreateView):
model = WebsiteDirective
form_class = WesiteDirectiveCreateForm
template_name = "musician/websites/website_create_option_form.html"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
website = get_object_or_404(Website, account=self.request.user, pk=self.kwargs["pk"])
kwargs['website'] = website
return kwargs
def get_success_url(self):
return reverse_lazy("musician:website-detail", kwargs={"pk": self.kwargs["pk"]})

View File

@ -4,6 +4,7 @@ from functools import lru_cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
from orchestra import plugins from orchestra import plugins
from orchestra.utils.python import import_class from orchestra.utils.python import import_class