-
{{ domain }}
{{ hosting_long }} as easy as possible
diff --git a/membership/admin.py b/membership/admin.py
index f69ccbef..3aefa780 100644
--- a/membership/admin.py
+++ b/membership/admin.py
@@ -1,20 +1,103 @@
+from django import forms
from django.contrib import admin
+from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
+from django.contrib.auth.forms import ReadOnlyPasswordHashField
+
from .models import CustomUser, StripeCustomer
-from django.contrib.auth.hashers import make_password
-class CustomUserAdmin(admin.ModelAdmin):
- fields = ('password', 'user_permissions', 'email', 'is_admin')
+# Refer https://docs.djangoproject.com/en/2.0/topics/auth/customizing/
+# for understanding custom auth user model
- def save_model(self, request, obj, form, change):
- password = form.cleaned_data.get('password')
- if not change:
- obj.validation_slug = make_password(None)
+class UserCreationForm(forms.ModelForm):
+ """A form for creating new users. Includes all the required
+ fields, plus a repeated password."""
+ password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
+ password2 = forms.CharField(label='Password confirmation',
+ widget=forms.PasswordInput)
- obj.set_password(password)
- obj.save()
- return obj
+ class Meta:
+ model = CustomUser
+ fields = ('email', 'user_permissions', 'email', 'is_admin')
+
+ def clean_password2(self):
+ # Check that the two password entries match
+ password1 = self.cleaned_data.get("password1")
+ password2 = self.cleaned_data.get("password2")
+ if password1 and password2 and password1 != password2:
+ raise forms.ValidationError("Passwords don't match")
+ return password2
+
+ def save(self, commit=True):
+ # Save the provided password in hashed format
+ user = super().save(commit=False)
+ user.set_password(self.cleaned_data["password1"])
+ if commit:
+ user.save()
+ return user
+
+
+class UserChangeForm(forms.ModelForm):
+ """A form for updating users. Includes all the fields on
+ the user, but replaces the password field with admin's
+ password hash display field.
+ """
+ password = ReadOnlyPasswordHashField(
+ label="Password",
+ help_text=(
+ "Raw passwords are not stored, so there is no way to see "
+ "this user's password, but you can change the password "
+ "using
this form .")
+ )
+
+ class Meta:
+ model = CustomUser
+ fields = ('email', 'password', 'is_admin')
+
+ def clean_password(self):
+ # Regardless of what the user provides, return the initial value.
+ # This is done here, rather than on the field, because the
+ # field does not have access to the initial value
+ return self.initial["password"]
+
+
+class CustomUserAdmin(BaseUserAdmin):
+ # The forms to add and change user instances
+ form = UserChangeForm
+ add_form = UserCreationForm
+
+ # The fields to be used in displaying the User model.
+ # These override the definitions on the base UserAdmin
+ # that reference specific fields on auth.User.
+ list_display = (
+ 'email', 'is_admin', 'is_superuser'
+ )
+ list_filter = ()
+ fieldsets = (
+ (None, {'fields': ('email',)}),
+ ('Change Password',
+ {'fields': ('password',),
+ 'description': "Raw passwords are not stored, so there is no way to "
+ "see this user's password, but you can change the "
+ "password using
this "
+ "form ."
+ }
+ ),
+ ('Permissions', {'fields': ('is_admin', 'user_permissions',
+ 'groups')}),
+ )
+ # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
+ # overrides get_fieldsets to use this attribute when creating a user.
+ add_fieldsets = (
+ (None, {
+ 'classes': ('wide',),
+ 'fields': ('email', 'password1', 'password2')}
+ ),
+ )
+ search_fields = ('email',)
+ ordering = ('email',)
+ filter_horizontal = ()
admin.site.register(CustomUser, CustomUserAdmin)
diff --git a/membership/migrations/0007_auto_20180213_0128.py b/membership/migrations/0007_auto_20180213_0128.py
new file mode 100644
index 00000000..0dd7b54a
--- /dev/null
+++ b/membership/migrations/0007_auto_20180213_0128.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-02-13 01:28
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import membership.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('membership', '0006_auto_20160526_0445'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='customuser',
+ name='validation_slug',
+ field=models.CharField(db_index=True, default=membership.models.get_validation_slug, max_length=50, unique=True),
+ ),
+ ]
diff --git a/membership/models.py b/membership/models.py
index 73804008..b3cbcd91 100644
--- a/membership/models.py
+++ b/membership/models.py
@@ -59,6 +59,10 @@ class MyUserManager(BaseUserManager):
return user
+def get_validation_slug():
+ return make_password(None)
+
+
class CustomUser(AbstractBaseUser, PermissionsMixin):
VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated'))
site = models.ForeignKey(Site, default=1)
@@ -66,8 +70,12 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0)
- validation_slug = models.CharField(db_index=True, unique=True,
- max_length=50)
+ # By default, we initialize the validation_slug with appropriate value
+ # This is required for User(page) admin
+ validation_slug = models.CharField(
+ db_index=True, unique=True, max_length=50,
+ default=get_validation_slug
+ )
is_admin = models.BooleanField(
_('staff status'),
default=False,
@@ -171,6 +179,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
# Simplest possible answer: All admins are staff
return self.is_admin
+ @is_staff.setter
+ def is_staff(self, value):
+ self._is_staff = value
+
class StripeCustomer(models.Model):
user = models.OneToOneField(CustomUser)
diff --git a/opennebula_api/serializers.py b/opennebula_api/serializers.py
index 07506a8b..cc52a15e 100644
--- a/opennebula_api/serializers.py
+++ b/opennebula_api/serializers.py
@@ -50,7 +50,7 @@ class VirtualMachineSerializer(serializers.Serializer):
disk_size = serializers.SerializerMethodField()
hdd_size = serializers.SerializerMethodField()
- sdd_size = serializers.SerializerMethodField()
+ ssd_size = serializers.SerializerMethodField()
ipv4 = serializers.SerializerMethodField()
ipv6 = serializers.SerializerMethodField()
vm_id = serializers.IntegerField(read_only=True, source='id')
@@ -90,7 +90,9 @@ class VirtualMachineSerializer(serializers.Serializer):
ssh_key=ssh_key,
specs=specs)
except OpenNebulaException as err:
- raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))
+ raise serializers.ValidationError(
+ "OpenNebulaException occured. {0}".format(err)
+ )
return manager.get_vm(opennebula_id)
@@ -104,7 +106,7 @@ class VirtualMachineSerializer(serializers.Serializer):
disk_size += int(disk.size)
return disk_size / 1024
- def get_sdd_size(self, obj):
+ def get_ssd_size(self, obj):
template = obj.template
disk_size = 0
for disk in template.disks:
@@ -165,7 +167,9 @@ class VirtualMachineSerializer(serializers.Serializer):
class VMTemplateSerializer(serializers.Serializer):
"""Serializer to map the VMTemplate instance into JSON format."""
- id = serializers.IntegerField(read_only=True, source='opennebula_vm_template_id')
+ id = serializers.IntegerField(
+ read_only=True, source='opennebula_vm_template_id'
+ )
name = serializers.CharField(read_only=True)
diff --git a/requirements.txt b/requirements.txt
index 7a325357..85a41841 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -34,6 +34,7 @@ django-meta==1.2
django-meta-mixin==0.3.0
django-model-utils==2.5
django-mptt==0.8.4
+django-multisite==1.4.1
django-parler==1.6.3
django-phonenumber-field==1.1.0
django-polymorphic==0.9.2
@@ -97,3 +98,4 @@ billiard==3.5.0.3
amqp==2.2.1
vine==1.1.4
cdist==4.7.0
+git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite
diff --git a/ungleich_page/cms_plugins.py b/ungleich_page/cms_plugins.py
index 47f296aa..660a363a 100644
--- a/ungleich_page/cms_plugins.py
+++ b/ungleich_page/cms_plugins.py
@@ -8,6 +8,7 @@ from .models import (
UngleichCustomerItem, UngleichHTMLOnly,
UngleichHeaderWithBackgroundImageSlider,
UngleichHeaderWithBackgroundVideoSliderItem,
+ UngleichFooter
)
@@ -295,3 +296,18 @@ class UngleichHTMLPlugin(CMSPluginBase):
)
context['instance'] = instance
return context
+
+
+@plugin_pool.register_plugin
+class UngleichFooterPlugin(CMSPluginBase):
+ name = "ungleich Footer Plugin"
+ model = UngleichFooter
+ render_template = "ungleich_page/ungleich/_footer.html"
+ cache = False
+
+ def render(self, context, instance, placeholder):
+ context = super(UngleichFooterPlugin, self).render(
+ context, instance, placeholder
+ )
+ context['instance'] = instance
+ return context
diff --git a/ungleich_page/migrations/0018_ungleichfooter.py b/ungleich_page/migrations/0018_ungleichfooter.py
new file mode 100644
index 00000000..37c33003
--- /dev/null
+++ b/ungleich_page/migrations/0018_ungleichfooter.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-02-08 15:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ungleich_page', '0017_auto_20171219_1856'),
+ ('cms', '0014_auto_20160404_1908'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UngleichFooter',
+ fields=[
+ ('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE,
+ parent_link=True, primary_key=True, serialize=False, to='cms.CMSPlugin')),
+ ('copyright_label', models.CharField(blank=True, default='',
+ help_text='Name of the company alongside the copyright year', max_length=100)),
+ ('link_text', models.CharField(
+ blank=True, help_text='Text for the link on the right part of footer', max_length=100, null=True)),
+ ('link_url', models.URLField(blank=True,
+ help_text='Url to the link in footer', null=True)),
+ ('twitter_url', models.URLField(
+ blank=True, help_text='If empty, twitter btn will not be visible', null=True)),
+ ('linkedin_url', models.URLField(
+ blank=True, help_text='If empty, linkedin btn will not be visible', null=True)),
+ ('github_url', models.URLField(
+ blank=True, help_text='If empty, github btn will not be visible', null=True)),
+ ('facebook_url', models.URLField(
+ blank=True, help_text='If empty, facebook btn will not be visible', null=True)),
+ ('youtube_url', models.URLField(
+ blank=True, help_text='If empty, youtube btn will not be visible', null=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=('cms.cmsplugin',),
+ ),
+ ]
diff --git a/ungleich_page/migrations/0019_merge.py b/ungleich_page/migrations/0019_merge.py
new file mode 100644
index 00000000..1cd6b7f9
--- /dev/null
+++ b/ungleich_page/migrations/0019_merge.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-02-08 20:10
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ungleich_page', '0018_ungleichfooter'),
+ ('ungleich_page', '0018_auto_20180105_1826'),
+ ]
+
+ operations = [
+ ]
diff --git a/ungleich_page/models.py b/ungleich_page/models.py
index b96afcb1..4ffd1a33 100644
--- a/ungleich_page/models.py
+++ b/ungleich_page/models.py
@@ -169,3 +169,38 @@ class UngleichHTMLOnly(CMSPlugin):
def __str__(self):
return self.name
+
+
+class UngleichFooter(CMSPlugin):
+ copyright_label = models.CharField(
+ max_length=100, default='', blank=True,
+ help_text='Name of the company alongside the copyright year'
+ )
+ link_text = models.CharField(
+ max_length=100, blank=True, null=True,
+ help_text='Text for the link on the right part of footer'
+ )
+ link_url = models.URLField(
+ blank=True, null=True,
+ help_text='Url to the link in footer'
+ )
+ twitter_url = models.URLField(
+ blank=True, null=True,
+ help_text='If empty, twitter btn will not be visible'
+ )
+ linkedin_url = models.URLField(
+ blank=True, null=True,
+ help_text='If empty, linkedin btn will not be visible'
+ )
+ github_url = models.URLField(
+ blank=True, null=True,
+ help_text='If empty, github btn will not be visible'
+ )
+ facebook_url = models.URLField(
+ blank=True, null=True,
+ help_text='If empty, facebook btn will not be visible'
+ )
+ youtube_url = models.URLField(
+ blank=True, null=True,
+ help_text='If empty, youtube btn will not be visible'
+ )
diff --git a/ungleich_page/static/ungleich_page/css/glasfaser.css b/ungleich_page/static/ungleich_page/css/glasfaser.css
index 0508bad1..490d600e 100644
--- a/ungleich_page/static/ungleich_page/css/glasfaser.css
+++ b/ungleich_page/static/ungleich_page/css/glasfaser.css
@@ -12,20 +12,35 @@
.navbar-default .navbar-nav>li>a {
text-transform: uppercase;
font-weight: 400;
- letter-spacing: 1px;
+ letter-spacing: 0.5px;
color: #777;
}
.navbar-transparent .navbar-nav>li>a {
color: #fff;
}
-.navbar-transparent .navbar-nav>li>a:hover,
-.navbar-transparent .navbar-nav>li>a:focus,
-.navbar-transparent .navbar-nav>li>a:focus:active {
- color: #fed136;
+.navbar-default .navbar-nav>li>a:focus,
+.navbar-default .navbar-nav>li>a:hover,
+.navbar-default .navbar-nav>li>a:active {
+ color: #333;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #888;
+}
+
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+ background: #ddd;
}
@media (min-width: 768px) {
+ .navbar-transparent .navbar-nav>li>a:focus,
+ .navbar-transparent .navbar-nav>li>a:hover,
+ .navbar-transparent .navbar-nav>li>a:active {
+ color: #e5e6e7;
+ }
+
.navbar-transparent {
border-color: transparent;
}
diff --git a/ungleich_page/templates/ungleich_page/glasfaser.html b/ungleich_page/templates/ungleich_page/glasfaser.html
index a1a01716..a8f11bd0 100644
--- a/ungleich_page/templates/ungleich_page/glasfaser.html
+++ b/ungleich_page/templates/ungleich_page/glasfaser.html
@@ -57,13 +57,13 @@
@@ -312,7 +312,6 @@
-
diff --git a/ungleich_page/templates/ungleich_page/ungleich/_footer.html b/ungleich_page/templates/ungleich_page/ungleich/_footer.html
new file mode 100644
index 00000000..f8770e57
--- /dev/null
+++ b/ungleich_page/templates/ungleich_page/ungleich/_footer.html
@@ -0,0 +1,47 @@
+
+
+
+
+ Copyright © {{instance.copyright_label}} {% now "Y" %}
+
+
+
+ {% if instance.link_text %}
+
+ {% endif %}
+
+
+
+
\ No newline at end of file
diff --git a/ungleich_page/templates/ungleich_page/ungleich_cms_page.html b/ungleich_page/templates/ungleich_page/ungleich_cms_page.html
index 707d0dab..f8d32f07 100644
--- a/ungleich_page/templates/ungleich_page/ungleich_cms_page.html
+++ b/ungleich_page/templates/ungleich_page/ungleich_cms_page.html
@@ -41,7 +41,9 @@
{% placeholder 'Ungleich Page Contents' %}
- {% include "ungleich_page/includes/_footer.html" %}
+ {% placeholder 'Footer' or %}
+ {% include "ungleich_page/includes/_footer.html" %}
+ {% endplaceholder %}