Rafał Pitoń 11 лет назад
Родитель
Сommit
e699545aae

+ 3 - 2
misago/conf/defaults.py

@@ -102,9 +102,10 @@ INSTALLED_APPS = (
     'misago.acl',
     'misago.acl',
     'misago.core',
     'misago.core',
     'misago.conf',
     'misago.conf',
-    'misago.faker',
-    'misago.forums',
     'misago.markup',
     'misago.markup',
+    'misago.forums',
+    'misago.legal',
+    'misago.faker',
 )
 )
 
 
 MIDDLEWARE_CLASSES = (
 MIDDLEWARE_CLASSES = (

+ 0 - 0
misago/legal/__init__.py


+ 7 - 0
misago/legal/apps.py

@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class MisagoLegalConfig(AppConfig):
+    name = 'misago.legal'
+    label = 'misago_legal'
+    verbose_name = "Misago Legal"

+ 109 - 0
misago/legal/migrations/0001_initial.py

@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.utils.translation import ugettext as _
+
+from misago.conf.migrationutils import migrate_settings_group
+
+
+def create_legal_settings_group(apps, schema_editor):
+    migrate_settings_group(
+        apps,
+        {
+            'key': 'legal',
+            'name': _("Legal information"),
+            'description': _("Those settings allow you to set forum terms of "
+                             "service and privacy policy"),
+            'settings': (
+                {
+                    'setting': 'terms_of_service_title',
+                    'name': _("Terms title"),
+                    'legend': _("Terms of Service"),
+                    'description': _("Leave this field empty to "
+                                     "use default title."),
+                    'value': "",
+                    'field_extra': {
+                        'max_length': 255,
+                        'required': False,
+                    },
+                },
+                {
+                    'setting': 'terms_of_service_link',
+                    'name': _("Terms link"),
+                    'description': _("If terms of service are located "
+                                     "on other page, enter there its link."),
+                    'value': "",
+                    'field_extra': {
+                        'max_length': 255,
+                        'required': False,
+                    },
+                },
+                {
+                    'setting': 'terms_of_service',
+                    'name': _("Terms contents"),
+                    'description': _("Your forums can have custom terms of "
+                                     "service page. To create it, write or "
+                                     "paste here its contents. Full Misago "
+                                     "markup is available for formatting."),
+                    'value': "",
+                    'form_field': 'textarea',
+                    'field_extra': {
+                        'max_length': 128000,
+                        'required': False,
+                        'rows': 8,
+                    },
+                    'is_lazy': True,
+                },
+                {
+                    'setting': 'privacy_policy_title',
+                    'name': _("Policy title"),
+                    'legend': _("Privacy policy"),
+                    'description': _("Leave this field empty to "
+                                     "use default title."),
+                    'value': "",
+                    'field_extra': {
+                        'max_length': 255,
+                        'required': False,
+                    },
+                },
+                {
+                    'setting': 'privacy_policy_link',
+                    'name': _("Policy link"),
+                    'description': _("If privacy policy is located on "
+                                     "other page, enter there its link."),
+                    'value': "",
+                    'field_extra': {
+                        'max_length': 255,
+                        'required': False,
+                    },
+                },
+                {
+                    'setting': 'privacy_policy',
+                    'name': _("Policy contents"),
+                    'description': _("Your forums can have custom privacy "
+                                     "policy page. To create it, write or "
+                                     "paste here its contents. Full Misago "
+                                     "markup is available for formatting."),
+                    'value': "",
+                    'form_field': 'textarea',
+                    'field_extra': {
+                        'max_length': 128000,
+                        'required': False,
+                        'rows': 8,
+                    },
+                    'is_lazy': True,
+                },
+            )
+        })
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('misago_conf', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RunPython(create_legal_settings_group),
+    ]

+ 0 - 0
misago/legal/migrations/__init__.py


+ 1 - 0
misago/legal/models.py

@@ -0,0 +1 @@
+# Empty models file that triggers migrations for this app

+ 75 - 0
misago/legal/tests.py

@@ -0,0 +1,75 @@
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+from misago.conf import settings
+
+
+class TermsOfServiceTests(TestCase):
+    def tearDown(self):
+        settings.reset_settings()
+
+    def test_404_on_no_tos(self):
+        """TOS view returns 404 when no TOS is set"""
+        self.assertFalse(settings.terms_of_service_link)
+        self.assertFalse(settings.terms_of_service)
+
+        response = self.client.get(reverse('misago:terms_of_service'))
+        self.assertEqual(response.status_code, 404)
+
+    def test_301_on_link_tos(self):
+        """TOS view returns 302 redirect when link is set"""
+        settings.override_setting('terms_of_service_link', 'http://test.com')
+        settings.override_setting('terms_of_service', 'Lorem ipsum')
+        self.assertTrue(settings.terms_of_service_link)
+        self.assertTrue(settings.terms_of_service)
+
+        response = self.client.get(reverse('misago:terms_of_service'))
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://test.com')
+
+    def test_200_on_link_tos(self):
+        """TOS view returns 200 when custom tos content is set"""
+        settings.override_setting('terms_of_service_title', 'Test ToS')
+        settings.override_setting('terms_of_service', 'Lorem ipsum dolor')
+        self.assertTrue(settings.terms_of_service_title)
+        self.assertTrue(settings.terms_of_service)
+
+        response = self.client.get(reverse('misago:terms_of_service'))
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('Test ToS', response.content)
+        self.assertIn('Lorem ipsum dolor', response.content)
+
+
+class PrivacyPolicyTests(TestCase):
+    def tearDown(self):
+        settings.reset_settings()
+
+    def test_404_on_no_policy(self):
+        """policy view returns 404 when no policy is set"""
+        self.assertFalse(settings.privacy_policy_link)
+        self.assertFalse(settings.privacy_policy)
+
+        response = self.client.get(reverse('misago:privacy_policy'))
+        self.assertEqual(response.status_code, 404)
+
+    def test_301_on_link_policy(self):
+        """policy view returns 302 redirect when link is set"""
+        settings.override_setting('privacy_policy_link', 'http://test.com')
+        settings.override_setting('privacy_policy', 'Lorem ipsum')
+        self.assertTrue(settings.privacy_policy_link)
+        self.assertTrue(settings.privacy_policy)
+
+        response = self.client.get(reverse('misago:privacy_policy'))
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://test.com')
+
+    def test_200_on_link_policy(self):
+        """policy view returns 200 when custom tos content is set"""
+        settings.override_setting('privacy_policy_title', 'Test Policy')
+        settings.override_setting('privacy_policy', 'Lorem ipsum dolor')
+        self.assertTrue(settings.privacy_policy_title)
+        self.assertTrue(settings.privacy_policy)
+
+        response = self.client.get(reverse('misago:privacy_policy'))
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('Test Policy', response.content)
+        self.assertIn('Lorem ipsum dolor', response.content)

+ 7 - 0
misago/legal/urls.py

@@ -0,0 +1,7 @@
+from django.conf.urls import patterns, url
+
+
+urlpatterns = patterns('misago.legal.views',
+    url(r'^terms-of-service/$', 'terms', name='terms_of_service'),
+    url(r'^privacy-policy/$', 'privacy_policy', name='privacy_policy'),
+)

+ 57 - 0
misago/legal/views.py

@@ -0,0 +1,57 @@
+from hashlib import md5
+
+from django.http import Http404
+from django.shortcuts import redirect, render
+from django.utils.translation import gettext as _
+
+from misago.conf import settings
+from misago.core.cache import cache
+from misago.markup import common_flavour
+
+
+def get_parsed_content(setting_name):
+    cache_name = 'misago_legal_%s' % setting_name
+    cached_content = cache.get(cache_name)
+
+    unparsed_content = settings.get_lazy_setting(setting_name)
+
+    checksum_source = '%s:%s' % (unparsed_content, settings.SECRET_KEY)
+    unparsed_checksum = md5(checksum_source).hexdigest()
+
+    if cached_content and cached_content.get('checksum') == unparsed_checksum:
+        return cached_content['parsed']
+    else:
+        cached_content = {
+            'checksum': unparsed_checksum,
+            'parsed': common_flavour(unparsed_content),
+        }
+        cache.set(cache_name, cached_content)
+        return cached_content['parsed']
+
+
+def terms(request):
+    if not (settings.terms_of_service or settings.terms_of_service_link):
+        raise Http404()
+
+    if settings.terms_of_service_link:
+        return redirect(settings.terms_of_service_link)
+
+    parsed_content = get_parsed_content('terms_of_service')
+    return render(request, 'misago/legal/terms_of_service.html', {
+        'title': settings.terms_of_service_title or _("Terms of service"),
+        'content': parsed_content,
+    })
+
+
+def privacy_policy(request):
+    if not (settings.privacy_policy or settings.privacy_policy_link):
+        raise Http404()
+
+    if settings.privacy_policy_link:
+        return redirect(settings.privacy_policy_link)
+
+    parsed_content = get_parsed_content('privacy_policy')
+    return render(request, 'misago/legal/privacy_policy.html', {
+        'title': settings.privacy_policy_title or _("Privacy policy"),
+        'content': parsed_content,
+    })

+ 68 - 43
misago/static/misago/css/misago/footer.less

@@ -14,65 +14,90 @@
 }
 }
 
 
 
 
-//== Custom footer message
+//== Footer menu
 //
 //
 //**
 //**
-.custom-footer {
-  color: @footer-color;
-}
+.main-footer {
+  .footer-nav {
+    margin: 0px;
+    overflow: auto;
+    padding: 0px;
+
+    text-align: center;
 
 
+    li {
+      display: inline-block;
+      padding: @padding-large-vertical @padding-xs-horizontal;
+      padding-bottom: @padding-small-vertical;
+
+      a {
+        &:link, &:visited {
+          color: @footer-link-color;
+          text-decoration: underline;
+        }
+
+        &:hover, &:active {
+          color: @footer-link-hover-color;
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
 
 
 //== Misago branding
 //== Misago branding
 //
 //
 //**
 //**
-.misago-branding {
-  a {
-    display: inline-block;
+.main-footer {
+  .misago-branding {
+    a {
+      display: inline-block;
 
 
-    color: @text-color;
-    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-    font-size: @misago-branding-size;
+      color: @text-color;
+      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      font-size: @misago-branding-size;
 
 
-    .brand-border {
-      background: @text-color;
-      border-radius: @misago-branding-size * 0.15;
-      display: inline-block;
-      margin-right: @misago-branding-size * 0.1;
-      position: relative;
-      top: @misago-branding-size * 0.18;
-      overflow: hidden;
-      width: @misago-branding-size + 3px;
-      height: @misago-branding-size + 3px;
-
-      &>span {
+      .brand-border {
+        background: @text-color;
+        border-radius: @misago-branding-size * 0.15;
         display: inline-block;
         display: inline-block;
-        position: absolute;
-        bottom: -20%;
-        right: -18%;
-        .rotate(-45deg);
-
-        color: @body-bg;
-        font-size: @misago-branding-size * 1.2;
-        font-weight: bold;
+        margin-right: @misago-branding-size * 0.1;
+        position: relative;
+        top: @misago-branding-size * 0.18;
+        overflow: hidden;
+        width: @misago-branding-size + 3px;
+        height: @misago-branding-size + 3px;
+
+        &>span {
+          display: inline-block;
+          position: absolute;
+          bottom: -20%;
+          right: -18%;
+          .rotate(-45deg);
+
+          color: @body-bg;
+          font-size: @misago-branding-size * 1.2;
+          font-weight: bold;
+        }
       }
       }
-    }
 
 
-    .subscript {
-      display: inline-block;
-      position: relative;
-      bottom: (@misago-branding-size  - (@misago-branding-size * 0.6)) / 2 - 1px;
+      .subscript {
+        display: inline-block;
+        position: relative;
+        bottom: (@misago-branding-size  - (@misago-branding-size * 0.6)) / 2 - 1px;
 
 
-      font-size: @misago-branding-size * 0.6;
-    }
+        font-size: @misago-branding-size * 0.6;
+      }
 
 
-    &, &:link, &:visited {
-      .opacity(0.4);
-    }
+      &, &:link, &:visited {
+        .opacity(0.4);
+      }
 
 
-    &:active, &:hover {
-      .opacity(1);
+      &:active, &:hover {
+        .opacity(1);
 
 
-      text-decoration: none;
+        text-decoration: none;
+      }
     }
     }
   }
   }
 }
 }

+ 5 - 1
misago/static/misago/css/misago/variables.less

@@ -169,13 +169,17 @@
 
 
 //== Page footer
 //== Page footer
 //
 //
-//## For each of Bootstrap's buttons, define text, background and border color.
+//## Footer appearance
 
 
 @footer-color:                   darken(@body-bg, 35%);
 @footer-color:                   darken(@body-bg, 35%);
 
 
 // Size of Misago's branding in footer
 // Size of Misago's branding in footer
 @misago-branding-size:           @font-size-large * 1.6;
 @misago-branding-size:           @font-size-large * 1.6;
 
 
+// Footer links
+@footer-link-color:              @gray-light;
+@footer-link-hover-color:        @gray-darker;
+
 
 
 //== Buttons
 //== Buttons
 //
 //

+ 14 - 0
misago/templates/misago/footer.html

@@ -1,6 +1,20 @@
 <footer class="main-footer">
 <footer class="main-footer">
   <div class="container">
   <div class="container">
 
 
+    <ul class="footer-nav">
+      {% if misago_settings.terms_of_service or misago_settings.terms_of_service_link %}
+      <li>
+        <a href="{% url 'misago:terms_of_service' %}">Terms of service</a>
+      </li>
+      {% endif %}
+
+      {% if misago_settings.privacy_policy or misago_settings.privacy_policy_link %}
+      <li>
+        <a href="{% url 'misago:privacy_policy' %}">Privacy policy</a>
+      </li>
+      {% endif %}
+    </ul>
+
     <div class="misago-branding text-center">
     <div class="misago-branding text-center">
       <a href="http://misago-project.org">
       <a href="http://misago-project.org">
         <span class="subscript">powered by</span>
         <span class="subscript">powered by</span>

+ 25 - 0
misago/templates/misago/legal/privacy_policy.html

@@ -0,0 +1,25 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{{ title }} | {{ block.super }}{% endblock %}
+
+
+{% block content %}
+<div class="page-header">
+  <div class="container">
+    <h1>
+      <span class="fa fa-ticket">
+      {{ title }}
+    </h1>
+  </div>
+</div>
+
+<div class="container">
+  <div class="misago-markup">
+
+    {{ content|safe }}
+
+  </div>
+</div>
+{% endblock content %}

+ 25 - 0
misago/templates/misago/legal/terms_of_service.html

@@ -0,0 +1,25 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{{ title }} | {{ block.super }}{% endblock %}
+
+
+{% block content %}
+<div class="page-header">
+  <div class="container">
+    <h1>
+      <span class="fa fa-certificate">
+      {{ title }}
+    </h1>
+  </div>
+</div>
+
+<div class="container">
+  <div class="misago-markup">
+
+    {{ content|safe }}
+
+  </div>
+</div>
+{% endblock content %}

+ 1 - 0
misago/urls.py

@@ -12,6 +12,7 @@ urlpatterns = patterns('misago.core.views',
 
 
 # Register Misago Apps
 # Register Misago Apps
 urlpatterns += patterns('',
 urlpatterns += patterns('',
+    url(r'^', include('misago.legal.urls')),
     url(r'^', include('misago.users.urls')),
     url(r'^', include('misago.users.urls')),
 )
 )