diff --git a/README.md b/README.md
index 8e60ac6..4a0ba0e 100644
--- a/README.md
+++ b/README.md
@@ -1,171 +1,302 @@
Django User Map
=================
-[![Stories in Ready](https://badge.waffle.io/aifdr/inasafe-user-map.png?label=ready&title=Ready)](http://waffle.io/aifdr/inasafe-user-map)
-[![Develop Branch](https://api.travis-ci.org/AIFDR/inasafe-user-map.svg?branch=develop)](https://travis-ci.org/AIFDR/inasafe-user-map)
-[![Coverage Status](https://coveralls.io/repos/AIFDR/inasafe-user-map/badge.png?branch=develop)](https://coveralls.io/r/AIFDR/inasafe-user-map?branch=develop)
+Django User Map is a reusable django application for making community user's map. This app relies on the active auth user model and extend that model with OneToOne relationship. Users can add themselves on the map by providing some additional information:
+1. Location on the map
+2. Roles (the choices can be configured through setting)
+3. Image, and
+4. Website
-A django application for making community user's map. Users can
-add themselves on the map by providing some information:
+Read here for the documentation: www.akbargumbira.com/django-user-map
-1. Name
-2. E-mail - will be used for authentication
-3. Password - will be used for authentication
-4. Website
-5. Role - The choices can be configured through setting.
-6. Location on the map
-
-Live site: http://users.inasafe.org
-
-Installation
-============
-1. Install django-user-map with pip:
- ```
- pip install django-user-map
- ```
-
-2. Make sure you have all of these items in INSTALLED_APPS of your django
- project settings.py:
- ```
- INSTALLED_APPS = (
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django.contrib.gis',
- 'user_map',
- 'leaflet',
- 'bootstrapform'
- )
- ```
-
-3. Include user-map URLconf in your project urls.py with namespace user_map (required) e.g:
- ```
- url(r'^user-map/', include('user_map.urls', namespace='user_map')),
- ```
-
-3. Add authentication user model and authentication backend in your django
- project settings.py:
- ```
- AUTH_USER_MODEL = 'user_map.User'
- AUTHENTICATION_BACKENDS = [
- 'user_map.auth_backend.UserMapAuthBackend',
- 'django.contrib.auth.backends.ModelBackend']
- ```
-
-4. Make sure to add template context processors needed by user-map:
- ```
- TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.contrib.auth.context_processors.auth',
- 'django.core.context_processors.request',
- 'django.contrib.messages.context_processors.messages',
- 'user_map.context_processors.user_map_settings',
- )
- ```
-
-5. Make sure to add mail server configuration on your project's settings.py
- so that this apps can send e-mail for some routines e.g sending confirmation
- e-mail after registration. If you are going to use SMTP server using your
- Gmail account, the configuration looks like this:
- ```
- EMAIL_USE_TLS = True
- EMAIL_HOST = 'smtp.gmail.com'
- EMAIL_PORT = 587
- EMAIL_HOST_USER = 'YOUR GMAIL ADDRESS'
- EMAIL_HOST_PASSWORD = 'YOUR GMAIL PASSWORD'
- DEFAULT_FROM_MAIL = 'MAIL ADDRESS AS THE DEFAULT SENDER'
- ```
-
-6. Run ```python manage.py migrate``` to create the user_map models.
-
-7. Create a superuser so that you can log in to django admin to administer
-user:
- ```python manage.py createsuperuser```
-
-7. Run ```python manage.py runserver``` to start the development server.
-
-8. Visit http://127.0.0.1:8000/user-map/ to open the apps.
-
-9. Visit your admin page (the default is http://127.0.0.1:8000/admin) to
-manage user as an admin.
-
-
-Apps Configurations
-==================
-
-Tile Layer
-------------
-
-You can configure the basemap of the form that uses LeafletWidget and the
-basemap of the homepage by adding 'LEAFLET_CONFIG' in settings.py e.g:
+## Installation
+* Install django-user-map with pip:
+
+```python
+pip install django-user-map
```
-LEAFLET_CONFIG = {
- 'TILES': [
- (
- 'OpenStreetMap', # The title
- 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', # The tile URL
- ('© OpenStreetMap and contributors, under an '
- 'open license ') # The attribution
+
+* Make sure you have these modules in INSTALLED_APPS of your django project settings.py:
+
+```python
+INSTALLED_APPS = (
+ 'django.contrib.gis',
+ 'user_map',
+ 'leaflet',
+ 'bootstrapform',
+ 'rest_framework',
+ 'rest_framework_gis'
+)
+```
+
+* Include user-map URLconf in your project urls.py with namespace user_map (required) e.g:
+
+```python
+url(r'^user-map/', include('user_map.urls', namespace='user_map')),
+```
+
+* Configure user map with USER_MAP variable in your project's settings.py or override some templates. See [Configurations](#configurations) section for further information.
+
+* Run ```python manage.py migrate user_map``` to migrate the ```user_map```
+models.
+
+* Visit the URL that you set before to see the apps.
+
+
+## Configurations
+You can configure Django User Map with one single USER_MAP variable in project's settings.py. The setting below is the default. Add this default to your project's setting and configure the necessary bits that you want to change:
+
+```python
+USER_MAP = {
+ 'project_name': 'Django',
+ 'favicon_file': '',
+ 'login_view': 'django.contrib.auth.views.login',
+ 'marker': {
+ # See leaflet icon valid options here:
+ # http://leafletjs.com /reference.html#icon-iconurl
+ 'iconUrl': 'static/user_map/img/user-icon.png',
+ 'shadowUrl': 'static/user_map/img/shadow-icon.png',
+ 'iconSize': [19, 32],
+ 'shadowSize': [42, 35],
+ 'iconAnchor': [10, 0],
+ 'shadowAnchor': [12, 0],
+ },
+ 'leaflet_config': {
+ 'TILES': [(
+ # The title
+ 'MapQuest',
+ # Tile's URL
+ 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
+ # More valid leaflet option are passed here
+ # See here: http://leafletjs.com/reference.html#tilelayer
+ {
+ 'attribution':
+ '© OpenStreetMap'
+ ' and contributors, under an open license . Tiles Courtesy of '
+ 'MapQuest OpenStreetMap'
+ ' and contributors, under an open license . Tiles Courtesy of '
+ 'MapQuest
+ The data you enter on this site may be visible to others. We suggest that
+ you approximate your physical location to the nearest
+ town or major center. Parts of your data will be made available for
+ others to download and use. Click the REST API link to see the data that are
+ available for public to use.
+
+```
+
+In the new ```data_privacy.html``` file, copy that and edit the wording yourself. Note that the ```
-{% endblock js_container %}
diff --git a/user_map/templates/user_map/account/login.html b/user_map/templates/user_map/account/login.html
deleted file mode 100644
index 33d9b6e..0000000
--- a/user_map/templates/user_map/account/login.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% extends "user_map/base_page.html" %}
-{% load leaflet_tags %}
-{% load bootstrap %}
-
-{% block head_resources %}
- {{ block.super }}
- {% leaflet_js plugins="forms" %}
- {% leaflet_css plugins="forms" %}
-{% endblock head_resources %}
-
-{% block main_content %}
- {{ block.super }}
-
-{% endblock main_content %}
-
-{% block js_container %}
- {{ block.super }}
-{% endblock js_container %}
diff --git a/user_map/templates/user_map/account/password_reset_complete.html b/user_map/templates/user_map/account/password_reset_complete.html
deleted file mode 100644
index c83b429..0000000
--- a/user_map/templates/user_map/account/password_reset_complete.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends 'user_map/base_page.html' %}
-
-{% block main_content %}
-
-
-
-
-
-
Your password has been reset. You may go ahead and log in now.
-
Log in
-
-
-{% endblock main_content%}
-
diff --git a/user_map/templates/user_map/account/password_reset_confirm.html b/user_map/templates/user_map/account/password_reset_confirm.html
deleted file mode 100644
index aaacc82..0000000
--- a/user_map/templates/user_map/account/password_reset_confirm.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% extends 'user_map/base_page.html' %}
-{% load bootstrap %}
-
-{% block title %}Reset Password{% endblock %}
-
-{% block main_content %}
-
-
-
-
-
- {% if validlink %}
-
- {% else %}
-
Ooops! This reset link is no longer valid!
- {% endif %}
-
-{% endblock main_content %}
diff --git a/user_map/templates/user_map/account/password_reset_done.html b/user_map/templates/user_map/account/password_reset_done.html
deleted file mode 100644
index ed38bee..0000000
--- a/user_map/templates/user_map/account/password_reset_done.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends 'user_map/base_page.html' %}
-
-{% block main_content %}
-
-
-
-
-
-
We've e-mailed you instructions for setting your password to the e-mail address you submitted.
-
You should be receiving it shortly.
-
-
- If you don't receive an email, please make sure you've entered the
- address you registered with, and check your spam folder.
-
-
-{% endblock main_content%}
diff --git a/user_map/templates/user_map/account/password_reset_email.html b/user_map/templates/user_map/account/password_reset_email.html
deleted file mode 100644
index c5d627c..0000000
--- a/user_map/templates/user_map/account/password_reset_email.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% autoescape off %}
-Hi {{ user.name}},
-
-You're receiving this email because you requested a password reset for your user account at {{ site_name }}
-Please go to the following page to set a new password:
-{{ protocol}}://{{ domain }}{% url 'user_map:password_reset_confirm' uidb64=uid token=token %}
-
-Thanks for using our site!
-{% endautoescape %}
diff --git a/user_map/templates/user_map/account/password_reset_form.html b/user_map/templates/user_map/account/password_reset_form.html
deleted file mode 100644
index 3e0399d..0000000
--- a/user_map/templates/user_map/account/password_reset_form.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends 'user_map/base_page.html' %}
-{% load bootstrap %}
-
-{% block title %}Reset Password {% endblock %}
-
-{% block main_content %}
-
-{% endblock main_content %}
diff --git a/user_map/templates/user_map/account/registration.html b/user_map/templates/user_map/account/registration.html
deleted file mode 100644
index 98fca09..0000000
--- a/user_map/templates/user_map/account/registration.html
+++ /dev/null
@@ -1,40 +0,0 @@
-{% extends "user_map/base_page.html" %}
-{% load leaflet_tags %}
-{% load bootstrap %}
-
-{% block head_resources %}
- {{ block.super }}
- {% leaflet_js plugins="forms" %}
- {% leaflet_css plugins="forms" %}
-{% endblock head_resources %}
-
-{% block main_content %}
-
-{% endblock main_content %}
diff --git a/user_map/templates/user_map/account/registration_confirmation_email.html b/user_map/templates/user_map/account/registration_confirmation_email.html
deleted file mode 100644
index 9724de3..0000000
--- a/user_map/templates/user_map/account/registration_confirmation_email.html
+++ /dev/null
@@ -1,19 +0,0 @@
-Dear {{ user.name }},
-
-Thank you for adding yourself to the {{ project_name }} User Map at
-{{ site_name }}. To activate your account, please click this link:
-{{ protocol}}://{{ domain }}{% url 'user_map:confirm_registration' uid=uid key=key %}
-
-Please note that the following information in your user record will be freely downloadable
-by anyone visiting the user map page:
- - Your name
- - your location (longitude, latitude)
- - your role (user, developer, trainer)
-
-If you are concerned about your privacy, we recommend you edit your record (you can
-do so using the link provided below) and generalise the position of your point (for example
-to the centre of your nearest town / state or province or country).
-
-Thank you for taking the time to register yourself on our site!
-
-Regards
diff --git a/user_map/templates/user_map/base.html b/user_map/templates/user_map/base.html
index d90e684..741e731 100644
--- a/user_map/templates/user_map/base.html
+++ b/user_map/templates/user_map/base.html
@@ -1,37 +1,40 @@
{% load staticfiles %}
+{% load leaflet_tags %}
-
-
-
-
- {% block title %} {{ PROJECT_NAME }} User Map{% endblock %}
+
+
+
+
+ {% block title %} {{ PROJECT_NAME }} User Map{% endblock %}
- {% block head_resources %}
-
-
-
+ {% block head_resources %}
+ {% leaflet_js %}
+ {% leaflet_css %}
+
+
+
-
-
-
-
- {% endblock head_resources %}
-
+
+
+
+
-
-
- {% block navbar %}
- {% endblock navbar %}
-
+ {% endblock head_resources %}
+
- {% block main_content %}
- {% endblock main_content %}
+
+
+ {% include 'user_map/navigation.html' %}
+
- {% block footer %}
- {% endblock footer %}
+ {% block main_content %}
+ {% endblock main_content %}
- {% block js_container %}
- {% endblock js_container %}
+ {% block footer %}
+ {% endblock footer %}
+
+ {% block js_container %}
+ {% endblock js_container %}
diff --git a/user_map/templates/user_map/base_page.html b/user_map/templates/user_map/base_page.html
index 90b444c..55110fd 100644
--- a/user_map/templates/user_map/base_page.html
+++ b/user_map/templates/user_map/base_page.html
@@ -1,46 +1,6 @@
{% extends "user_map/base.html" %}
{% load staticfiles %}
-{% block navbar %}
-
-
-
-
-
-{% endblock navbar %}
-
{% block main_content %}
{% endblock main_content %}
diff --git a/user_map/templates/user_map/data_privacy.html b/user_map/templates/user_map/data_privacy.html
index 93136d3..d98523b 100644
--- a/user_map/templates/user_map/data_privacy.html
+++ b/user_map/templates/user_map/data_privacy.html
@@ -2,11 +2,6 @@
The data you enter on this site may be visible to others. We suggest that
you approximate your physical location to the nearest
town or major center. Parts of your data will be made available for
- others to download and use. We will not share:
-
- your email address
- your password
-
- If you would like your data removed from this site, please log in to your
- account.
+ others to download and use. Click the REST API link to see the data that are
+ available for public to use.
diff --git a/user_map/templates/user_map/filter_menu.html b/user_map/templates/user_map/filter_menu.html
new file mode 100644
index 0000000..c9b1909
--- /dev/null
+++ b/user_map/templates/user_map/filter_menu.html
@@ -0,0 +1,8 @@
+
diff --git a/user_map/templates/user_map/index.html b/user_map/templates/user_map/index.html
index 31da34e..2ead2d2 100644
--- a/user_map/templates/user_map/index.html
+++ b/user_map/templates/user_map/index.html
@@ -3,51 +3,16 @@
{% load leaflet_tags %}
{% block navbar %}
-
-
-
-
-
{% endblock navbar%}
{% block head_resources %}
{{ block.super }}
- {% leaflet_js %}
- {% leaflet_css %}
+
{% endblock head_resources %}
@@ -61,21 +26,30 @@
{{ user_menu_button | safe }}
{{ information_modal | safe }}
{{ data_privacy_content | safe }}
- {{ legend | safe }}
- {{ user_form_template | safe }}
+ {{ filter_menu | safe }}
+ {{ user_info_popup_template | safe }}
+
+ {% if messages %}
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
{% endblock main_content %}
{% block js_container %}
{% endblock js_container %}
diff --git a/user_map/templates/user_map/information.html b/user_map/templates/user_map/information.html
deleted file mode 100644
index b649fa9..0000000
--- a/user_map/templates/user_map/information.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends "user_map/base_page.html" %}
-
-{% block main_content %}
-
-
- {{ information }}
-
-{% endblock main_content %}
diff --git a/user_map/templates/user_map/legend.html b/user_map/templates/user_map/legend.html
deleted file mode 100644
index e07498f..0000000
--- a/user_map/templates/user_map/legend.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% load staticfiles %}
-
-
diff --git a/user_map/templates/user_map/navigation.html b/user_map/templates/user_map/navigation.html
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/user_map/templates/user_map/navigation.html
@@ -0,0 +1 @@
+
diff --git a/user_map/templates/user_map/user_add_update.html b/user_map/templates/user_map/user_add_update.html
new file mode 100644
index 0000000..85c30bc
--- /dev/null
+++ b/user_map/templates/user_map/user_add_update.html
@@ -0,0 +1,29 @@
+{% extends "user_map/base_page.html" %}
+{% load leaflet_tags %}
+{% load bootstrap %}
+
+{% block head_resources %}
+ {{ block.super }}
+ {% leaflet_js plugins="forms" %}
+ {% leaflet_css plugins="forms" %}
+{% endblock head_resources %}
+
+{% block main_content %}
+
+{% endblock main_content %}
diff --git a/user_map/templates/user_map/user_info_popup_content.html b/user_map/templates/user_map/user_info_popup_content.html
index a4522b7..67fd928 100644
--- a/user_map/templates/user_map/user_info_popup_content.html
+++ b/user_map/templates/user_map/user_info_popup_content.html
@@ -1,6 +1,19 @@
-{% spaceless %}
- {{ user.name }}
- {% if user.website != "" %}
- Website
- {% endif %}
-{% endspaceless %}
+
diff --git a/user_map/templates/user_map/user_menu_button.html b/user_map/templates/user_map/user_menu_button.html
index 6acda23..3c71213 100644
--- a/user_map/templates/user_map/user_menu_button.html
+++ b/user_map/templates/user_map/user_menu_button.html
@@ -1,48 +1,26 @@
-{% if not user.is_authenticated %}
-
+{% if not user.is_authenticated or not is_mapped %}
+
{% else %}
-
-{% endif %}
-
-{% if user.is_authenticated %}
-
{% endif %}
-
-
-{% if not user.is_authenticated %}
-
-{% endif %}
diff --git a/user_map/templates/user_map/users.json b/user_map/templates/user_map/users.json
deleted file mode 100644
index f15dea2..0000000
--- a/user_map/templates/user_map/users.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "users": {
- "type": "FeatureCollection",
- "features": [ {% for user in users %}
- {
- "type": "Feature",
- "properties": {
- "name": "{{ user.name }}",
- "popupContent": "{% spaceless %} {% include 'user_map/user_info_popup_content.html'%} {% endspaceless %}"
- },
- "geometry": {
- "type": "Point",
- "coordinates": [{{ user.location.x }}, {{ user.location.y }}]
- }
- }{% if not forloop.last %},{% endif %}{% endfor %}
- ]
- }
-}
diff --git a/user_map/tests/model_factories.py b/user_map/tests/model_factories.py
index af509ce..421f1c9 100644
--- a/user_map/tests/model_factories.py
+++ b/user_map/tests/model_factories.py
@@ -1,10 +1,21 @@
# coding=utf-8
"""Model factories definition for models."""
+from django.conf import settings
from django.contrib.gis.geos import Point
+
import factory
from factory import DjangoModelFactory
-from user_map.models import Role, User
+from user_map.models import Role, UserMap
+
+
+class UserFactory(DjangoModelFactory):
+ class Meta:
+ model = settings.AUTH_USER_MODEL
+
+ username = factory.Sequence(lambda n: 'user{0}'.format(n))
+ password = factory.PostGenerationMethodCall(
+ 'set_password', 'default_password')
class RoleFactory(DjangoModelFactory):
@@ -13,21 +24,31 @@ class Meta:
"""Meta definition."""
model = Role
+ id = factory.Sequence(lambda n: n)
name = factory.Sequence(lambda n: 'Role %s' % n)
- sort_number = 1
+ badge = factory.Sequence(
+ lambda n: 'path/to/badge/role%s' % n)
-class UserFactory(DjangoModelFactory):
- """Factory class for User Model"""
+class UserMapFactory(DjangoModelFactory):
+ """Factory class for UserMap Model"""
class Meta:
""""Meta definition."""
- model = User
- django_get_or_create = ('email',)
+ model = UserMap
# Taking others as default value defined in model but not these:
- name = 'John Doe'
- email = factory.Sequence(lambda n: 'john.doe%s@example.com' % n)
- password = factory.PostGenerationMethodCall(
- 'set_password', 'default_password')
+ user = factory.SubFactory(UserFactory)
location = Point(105.567, 123)
- role = factory.SubFactory(RoleFactory)
+ # image = factory.Sequence(
+ # lambda n: 'path/to/image/user%s' % n)
+
+ @factory.post_generation
+ def roles(self, create, extracted, **kwargs):
+ if not create:
+ # Simple build, do nothing.
+ return
+
+ if extracted:
+ # A list of groups were passed in, use them
+ for role in extracted:
+ self.roles.add(role)
diff --git a/user_map/tests/test_models.py b/user_map/tests/test_models.py
index a1d6875..20b1ca1 100644
--- a/user_map/tests/test_models.py
+++ b/user_map/tests/test_models.py
@@ -1,12 +1,17 @@
# coding=utf-8
"""Module related to test for all the models."""
-from django.test import TestCase
+from django.test import TransactionTestCase
+from django.contrib.gis.geos import Point
-from user_map.tests.model_factories import RoleFactory, UserFactory
+from user_map.tests.model_factories import (
+ RoleFactory, UserFactory, UserMapFactory)
+from user_map.models.role import Role
-class TestRole(TestCase):
+class TestRole(TransactionTestCase):
"""Class to test Role model."""
+ reset_sequences = True
+
def setUp(self):
pass
@@ -18,11 +23,20 @@ def test_create_role(self):
def test_read_role(self):
"""Method to test reading role."""
- role_name = 'Testing Role'
- role = RoleFactory.create(name=role_name)
- message = 'The role name should be %s, but it gives %s' % (
- role_name, role.name)
- self.assertEqual(role_name, role.name, message)
+ for i in range(3):
+ RoleFactory(__sequence=i)
+
+ for i in range(3):
+ role_name = 'Role %s' % i
+ role_badge = 'path/to/badge/role%s' % i
+
+ role = Role.objects.get(pk=i)
+ message = 'The role name should be %s, but it gives %s' % (
+ role_name, role.name)
+ self.assertEqual(role_name, role.name, message)
+ message = 'The role badge should be %s, but it gives %s' % (
+ role_badge, role.badge)
+ self.assertEqual(role_badge, role.badge, message)
def test_update_role(self):
"""Method to test updating role."""
@@ -43,55 +57,60 @@ def test_delete_role(self):
self.assertIsNone(role.id, message)
-class TestUser(TestCase):
+class TestUserMap(TransactionTestCase):
"""Class to test User model."""
def setUp(self):
pass
- def test_create_user(self):
- """Method to test user creation."""
- user = UserFactory.create()
- message = 'The user is not instantiated successfully.'
- self.assertIsNotNone(user.id, message)
-
- # email_updates, is_admin, is_confirmed = False
- message = 'email_updates must be False'
- self.assertFalse(user.email_updates, message)
- message = 'is_admin must be False'
- self.assertFalse(user.is_admin, message)
- message = 'is_confirmed must be False'
- self.assertFalse(user.is_confirmed, message)
-
- # is_active = True
- message = 'is_active must be True'
- self.assertTrue(user.is_active, message)
-
- def test_read_user(self):
- """Method to test reading user."""
- user_name = 'John Doe'
- user_website = 'www.johndoe.com'
- user = UserFactory.create(name=user_name, website=user_website)
- message = 'The user name should be %s, but it gives %s' % (
- user_name, user.name)
- self.assertEqual(user_name, user.name, message)
- message = 'The user website should be %s, but it gives %s' % (
- user_website, user.website)
- self.assertEqual(user_website, user.website, message)
-
- def test_update_user(self):
- """Method to test update user."""
- user = UserFactory.create()
- user_name = 'Updated John Doe'
- user.name = user_name
- user.save()
- message = 'The user name should be %s, but it gives %s' % (
- user_name, user.name)
- self.assertEqual(user_name, user.name, message)
+ def test_create_usermap(self):
+ """Method to test user map creation."""
+ user_map = UserMapFactory.create()
+ message = 'The user map is not instantiated successfully.'
+ self.assertIsNotNone(user_map.user, message)
+
+ def test_read_usermap(self):
+ """Method to test reading user map."""
+ user = UserFactory(username='John Doe')
+ location = Point(5, 5)
+ image = '/john/doe/image.png'
+
+ usermap = UserMapFactory.create(
+ user=user, location=location, image=image)
+
+ message = 'The username should be %s, but it gives %s' % (
+ user.username, usermap.user.username)
+ self.assertEqual(user.username, usermap.user.username, message)
+
+ message = 'The user location should be %s, but it gives %s' % (
+ location, usermap.location)
+ self.assertEqual(location, usermap.location, message)
+
+ message = 'The user image should be %s, but it gives %s' % (
+ image, usermap.image)
+ self.assertEqual(image, usermap.image, message)
+
+ def test_update_usermap(self):
+ """Method to test updating usermap."""
+ user_map = UserMapFactory()
+
+ new_location = Point(10, 10)
+ user_map.location = new_location
+ user_map.save()
+ message = 'The user map location should be %s, but it gives %s' % (
+ new_location, user_map.location)
+ self.assertEqual(new_location, user_map.location, message)
+
+ new_image = '/new/image.png'
+ user_map.image = new_image
+ user_map.save()
+ message = 'The user map image should be %s, but it gives %s' % (
+ new_image, user_map.image)
+ self.assertEqual(new_image, user_map.image, message)
def test_delete_user(self):
- """Method to test delete user."""
- user = UserFactory.create()
- self.assertIsNotNone(user.id)
- user.delete()
- message = 'The user is not deleted.'
- self.assertIsNone(user.id, message)
+ """Method to test deleting user map."""
+ user_map = UserMapFactory.create()
+ self.assertIsNotNone(user_map.pk)
+ user_map.delete()
+ message = 'The user map is not deleted.'
+ self.assertIsNone(user_map.pk, message)
diff --git a/user_map/tests/test_settings.py b/user_map/tests/test_settings.py
index 9909ef4..068f594 100644
--- a/user_map/tests/test_settings.py
+++ b/user_map/tests/test_settings.py
@@ -1,4 +1,6 @@
+# coding=utf-8
import os
+
local_path = lambda path: os.path.join(os.path.dirname(__file__), path)
SITE_ID = 1
@@ -14,43 +16,114 @@
}
}
-INSTALLED_APPS = [
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
'django.contrib.contenttypes',
- 'django.contrib.sites',
'django.contrib.sessions',
+ 'django.contrib.messages',
'django.contrib.staticfiles',
- 'django.contrib.auth',
- 'django.contrib.admin',
+
'django.contrib.gis',
+ 'django.contrib.sites',
+ 'user_map',
'leaflet',
'bootstrapform',
- 'user_map',
-]
+ 'rest_framework',
+ 'rest_framework_gis',
+)
ROOT_URLCONF = 'user_map.tests.urls'
-TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.contrib.auth.context_processors.auth',
- 'django.core.context_processors.request',
- 'django.contrib.messages.context_processors.messages',
- 'user_map.context_processors.user_map_settings',
-)
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
-)
+ 'django.middleware.security.SecurityMiddleware',
-AUTH_USER_MODEL = 'user_map.User'
-AUTHENTICATION_BACKENDS = [
- 'user_map.auth_backend.UserMapAuthBackend',
- 'django.contrib.auth.backends.ModelBackend']
+ 'django.contrib.auth.middleware.AuthenticationMiddleware'
+)
STATIC_ROOT = local_path('static/')
STATIC_URL = '/static/'
SECRET_KEY = 'django-user-map'
+
+USER_MAP = {
+ 'project_name': 'Test Project',
+ 'favicon_file': '',
+ 'login_view': 'django.contrib.auth.views.login',
+ 'marker': {
+ 'iconUrl': 'static/user_map/img/user-icon.png',
+ 'shadowUrl': 'static/user_map/img/shadow-icon.png',
+ 'iconSize': [19, 32],
+ 'shadowSize': [42, 35],
+ 'iconAnchor': [10, 0],
+ 'shadowAnchor': [12, 0],
+ },
+ 'leaflet_config': {
+ 'TILES': [(
+ # The title
+ 'MapQuest',
+ # Tile's URL
+ 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
+ # More valid leaflet option are passed here
+ # See here: http://leafletjs.com/reference.html#tilelayer
+ {
+ 'attribution':
+ '© OpenStreetMap'
+ ' and contributors, under an open license . Tiles Courtesy of '
+ 'MapQuest [0-9A-Za-z_\-]+)/(?P.+)/$',
- 'user_map.views.confirm_registration',
- name='confirm_registration'),
-
- url(r'^login$', 'user_map.views.login', name='login'),
- url(r'^logout$', 'user_map.views.logout', name='logout'),
- url(r'^update-profile$', 'user_map.views.update_user', name='update_user'),
- url(r'^delete-user$', 'user_map.views.delete_user', name='delete_user'),
-
- url(r'^password-reset/$', 'user_map.views.password_reset',
- name='password_reset'),
- url(r'^password-reset/done/$', 'user_map.views.password_reset_done',
- name='password_reset_done'),
- url(r'^password-reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P.+)/$',
- 'user_map.views.password_reset_confirm',
- name='password_reset_confirm'),
- url(r'^password-reset/complete/$',
- 'user_map.views.password_reset_complete',
- name='password_reset_complete'),
-
- url(r'^download$', 'user_map.views.download', name='download'),
-)
+urlpatterns = [
+ url(r'^$', user_map.views.IndexView.as_view(), name='index'),
+ url(r'^add',
+ login_required(
+ user_map.views.UserAddView.as_view(),
+ login_url=settings.USER_MAP['login_view']),
+ name='add'),
+ url(r'^update$',
+ login_required(
+ user_map.views.UserUpdateView.as_view(),
+ login_url=settings.USER_MAP['login_view']),
+ name='update'),
+ url(r'^usermaps/$', user_map.views.UserMapList.as_view(),
+ name='usermap-list'),
+]
diff --git a/user_map/views.py b/user_map/views.py
index 02a5c2d..a5086a6 100644
--- a/user_map/views.py
+++ b/user_map/views.py
@@ -1,419 +1,182 @@
# coding=utf-8
"""Views of the apps."""
-import csv
+from exceptions import AttributeError
import json
-from django.shortcuts import render, render_to_response
-from django.http import HttpResponse, HttpResponseRedirect, Http404
-from django.template import RequestContext, loader
-from django.forms.util import ErrorList
-from django.forms.forms import NON_FIELD_ERRORS
+from django.http import HttpResponseRedirect
+from django.template import loader
from django.core.urlresolvers import reverse
-from django.core.mail import send_mail
from django.contrib import messages
-from django.contrib.auth import (
- login as django_login,
- authenticate,
- logout as django_logout)
-from django.contrib.auth.views import (
- password_reset as django_password_reset,
- password_reset_done as django_password_reset_done,
- password_reset_confirm as django_password_reset_confirm,
- password_reset_complete as django_password_reset_complete)
-from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
-from django.contrib.auth.decorators import login_required
-from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
-from django.utils.encoding import force_bytes
-from django.contrib.sites.models import get_current_site
+from django.views.generic.edit import CreateView, UpdateView
+from django.core.exceptions import ObjectDoesNotExist
+from django.views.generic import TemplateView
+
+from rest_framework import generics
from user_map.forms import (
- RegistrationForm,
- LoginForm,
- BasicInformationForm,
- CustomPasswordResetForm)
-from user_map.models import User
+ UserMapForm)
+from user_map.models import UserMap, Role
from user_map.app_settings import (
- PROJECT_NAME, USER_ROLES, DEFAULT_FROM_MAIL, LEAFLET_TILES)
-from user_map.utilities.decorators import login_forbidden
-
-
-def index(request):
- """Index page of user map.
-
- :param request: A django request object.
- :type request: request
-
- :returns: Response will be a nice looking map page.
- :rtype: HttpResponse
- """
- information_modal = loader.render_to_string(
- 'user_map/information_modal.html')
- data_privacy_content = loader.render_to_string(
- 'user_map/data_privacy.html')
-
- user_menu_button = loader.render_to_string(
- 'user_map/user_menu_button.html', {'user': request.user})
-
- legend = loader.render_to_string(
- 'user_map/legend.html', {'user_roles': USER_ROLES})
-
- leaflet_tiles = dict(
- url=LEAFLET_TILES[1],
- attribution=LEAFLET_TILES[2]
- )
- context = {
- 'data_privacy_content': data_privacy_content,
- 'information_modal': information_modal,
- 'user_menu_button': user_menu_button,
- 'user_roles': json.dumps(USER_ROLES),
- 'legend': legend,
- 'leaflet_tiles': leaflet_tiles
- }
- return render(request, 'user_map/index.html', context)
-
-
-def get_users(request):
- """Return a json document of users with given role.
-
- This will only fetch users who have approved by email and still active.
-
- :param request: A django request object.
- :type request: request
- """
- if request.method == 'GET':
- # Get data:
- user_role = str(request.GET['user_role'])
-
- # Get user
- users = User.objects.filter(
- role__name=user_role,
- is_confirmed=True,
- is_active=True)
- users_json = loader.render_to_string(
- 'user_map/users.json', {'users': users})
-
- # Return Response
- return HttpResponse(users_json, content_type='application/json')
- else:
- raise Http404
-
-
-@login_forbidden
-def register(request):
- """User registration view.
-
- :param request: A django request object.
- :type request: request
- """
- if request.method == 'POST':
- form = RegistrationForm(data=request.POST)
- if form.is_valid():
- user = form.save()
-
- current_site = get_current_site(request)
- site_name = current_site.name
- domain = current_site.domain
- context = {
- 'project_name': PROJECT_NAME,
- 'protocol': 'http',
- 'domain': domain,
- 'site_name': site_name,
- 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
- 'user': user,
- 'key': user.key
+ LEAFLET_TILES, MARKER, FAVICON_FILE, PROJECT_NAME)
+from user_map.serializers import UserMapSerializer
+
+
+class BaseTemplateMixin(object):
+ """Mixin for Base Template."""
+
+ @property
+ def is_mapped(self):
+ """Get status is_mapped of the self.request.user."""
+ try:
+ # noinspection PyUnresolvedReferences
+ is_mapped = bool(self.request.user.usermap)
+ except (ObjectDoesNotExist, AttributeError):
+ is_mapped = False
+ return is_mapped
+
+ def get_context_data(self, **kwargs):
+ # noinspection PyUnresolvedReferences
+ context = super(BaseTemplateMixin, self).get_context_data(**kwargs)
+ context['FAVICON_PATH'] = FAVICON_FILE
+ context['PROJECT_NAME'] = PROJECT_NAME
+ return context
+
+
+class IndexView(BaseTemplateMixin, TemplateView):
+ """Index page of user map."""
+ template_name = 'user_map/index.html'
+
+ @property
+ def information_modal(self):
+ """Get information modal."""
+ return loader.render_to_string(
+ 'user_map/information_modal.html')
+
+ @property
+ def data_privacy(self):
+ """Get data privacy content."""
+ return loader.render_to_string(
+ 'user_map/data_privacy.html')
+
+ @property
+ def leaflet_tiles(self):
+ """Get leaflet tiles for the template."""
+ return dict(
+ url=LEAFLET_TILES[0][1],
+ option=LEAFLET_TILES[0][2]
+ )
+
+ @property
+ def user_popup(self):
+ """Get user popup template."""
+ return loader.render_to_string(
+ 'user_map/user_info_popup_content.html')
+
+ def get_user_menu(self):
+ """Get user menu at the left side."""
+ return loader.render_to_string(
+ 'user_map/user_menu_button.html',
+ {'user': self.request.user, 'is_mapped': self.is_mapped}
+ )
+
+ def get_filter_menu(self):
+ """Get filter menu at the right side."""
+ roles = Role.objects.all()
+ return loader.render_to_string(
+ 'user_map/filter_menu.html',
+ {'roles': roles}
+ )
+
+ def get_context_data(self, **kwargs):
+ context = super(IndexView, self).get_context_data(**kwargs)
+ context.update(
+ {
+ 'data_privacy_content': self.data_privacy,
+ 'information_modal': self.information_modal,
+ 'user_menu_button': self.get_user_menu(),
+ 'filter_menu': self.get_filter_menu(),
+ 'leaflet_tiles': json.dumps(self.leaflet_tiles),
+ 'is_mapped': self.is_mapped,
+ 'marker': json.dumps(MARKER),
+ 'user_info_popup_template': self.user_popup,
+ 'roles': json.dumps(list(Role.objects.all().values()))
}
- email = loader.render_to_string(
- 'user_map/account/registration_confirmation_email.html',
- context)
-
- subject = '%s User Registration' % PROJECT_NAME
- sender = '%s - No Reply <%s>' % (PROJECT_NAME, DEFAULT_FROM_MAIL)
- send_mail(
- subject, email, sender, [user.email], fail_silently=False)
-
- messages.success(
- request,
- ('Thank you for registering in our site! Please check your '
- 'email to confirm your registration'))
- return HttpResponseRedirect(reverse('user_map:register'))
- else:
- form = RegistrationForm()
- return render_to_response(
- 'user_map/account/registration.html',
- {'form': form},
- context_instance=RequestContext(request)
- )
-
-
-@login_forbidden
-def confirm_registration(request, uid, key):
- """The view containing form to reset password and process it.
-
- :param request: A django request object.
- :type request: request
-
- :param uid: A unique id for a user.
- :type uid: str
-
- :param key: Key to confirm the user.
- :type key: str
- """
- decoded_uid = urlsafe_base64_decode(uid)
- try:
- user = User.objects.get(pk=decoded_uid)
-
- if not user.is_confirmed:
- if user.key == key:
- user.is_confirmed = True
- user.save(update_fields=['is_confirmed'])
- information = (
- 'Congratulations! Your account has been successfully '
- 'confirmed. Please continue to log in.')
- else:
- information = (
- 'Your link is not valid. Please make sure that you use '
- 'confirmation link we sent to your email.')
- else:
- information = ('Your account is already confirmed. Please '
- 'continue to log in.')
- except (TypeError, ValueError, OverflowError, User.DoesNotExist):
- information = ('Your link is not valid. Please make sure that you use '
- 'confirmation link we sent to your email.')
-
- context = {
- 'page_header_title': 'Registration Confirmation',
- 'information': information
- }
- return render_to_response(
- 'user_map/information.html',
- context,
- context_instance=RequestContext(request)
- )
-
-
-@login_forbidden
-def login(request):
- """Login view.
-
- :param request: A django request object.
- :type request: request
- """
- if request.method == 'POST':
- next_page = request.GET.get('next', '')
- if next_page == '':
- next_page = reverse('user_map:index')
- form = LoginForm(data=request.POST)
- if form.is_valid():
- user = authenticate(
- email=request.POST['email'],
- password=request.POST['password']
- )
- if user is not None:
- if user.is_active and user.is_confirmed:
- django_login(request, user)
-
- return HttpResponseRedirect(next_page)
- if not user.is_active:
- errors = form._errors.setdefault(
- NON_FIELD_ERRORS, ErrorList())
- errors.append(
- 'The user is not active. Please contact our '
- 'administrator to resolve this.')
- if not user.is_confirmed:
- errors = form._errors.setdefault(
- NON_FIELD_ERRORS, ErrorList())
- errors.append(
- 'Please confirm you registration email first!')
- else:
- errors = form._errors.setdefault(
- NON_FIELD_ERRORS, ErrorList())
- errors.append(
- 'Please enter a correct email and password. '
- 'Note that both fields may be case-sensitive.')
- else:
- form = LoginForm()
-
- return render_to_response(
- 'user_map/account/login.html',
- {'form': form},
- context_instance=RequestContext(request))
-
-
-def logout(request):
- """Log out view.
-
- :param request: A django request object.
- :type request: request
- """
- django_logout(request)
- return HttpResponseRedirect(reverse('user_map:index'))
-
-
-@login_required(login_url='user_map:login')
-def update_user(request):
- """Update user view.
-
- :param request: A django request object.
- :type request: request
- """
- anchor_id = '#basic-information'
- if request.method == 'POST':
- if 'change_basic_info' in request.POST:
- anchor_id = '#basic-information'
- basic_info_form = BasicInformationForm(
- data=request.POST, instance=request.user)
- change_password_form = PasswordChangeForm(user=request.user)
- if basic_info_form.is_valid():
- user = basic_info_form.save()
- messages.success(
- request, 'You have successfully changed your information!')
- return HttpResponseRedirect(
- reverse('user_map:update_user') + anchor_id)
- else:
- anchor_id = '#basic-information'
- elif 'change_password' in request.POST:
- anchor_id = '#security'
- change_password_form = PasswordChangeForm(
- data=request.POST, user=request.user)
- basic_info_form = BasicInformationForm(instance=request.user)
- if change_password_form.is_valid():
- user = change_password_form.save()
- messages.success(
- request, 'You have successfully changed your password!')
- return HttpResponseRedirect(
- reverse('user_map:update_user') + anchor_id)
- else:
- anchor_id = '#security'
- else:
- basic_info_form = BasicInformationForm(instance=request.user)
- change_password_form = PasswordChangeForm(user=request.user)
-
- return render_to_response(
- 'user_map/account/edit_user.html',
- {
- 'basic_info_form': basic_info_form,
- 'change_password_form': change_password_form,
- 'anchor_id': anchor_id,
- },
- context_instance=RequestContext(request)
- )
-
-
-@login_required(login_url='user_map:index')
-def delete_user(request):
- """Delete user view.
-
- :param request: A django request object.
- :type request: request
- """
- if request.method == 'POST':
- user = request.user
- user.delete()
- django_logout(request)
-
- information = ('You have deleted your account. Please register to '
- 'this site any time you want.')
- context = {
- 'page_header_title': 'Delete Account',
- 'information': information
- }
- return render_to_response(
- 'user_map/information.html',
- context,
- context_instance=RequestContext(request)
)
- else:
- raise Http404
-
-
-@login_forbidden
-def password_reset(request):
- """The view for reset password that contains a form to ask for email.
-
- :param request: A django request object.
- :type request: request
- """
- return django_password_reset(
- request,
- password_reset_form=CustomPasswordResetForm,
- template_name='user_map/account/password_reset_form.html',
- email_template_name='user_map/account/password_reset_email.html',
- post_reset_redirect=reverse('user_map:password_reset_done'))
-
-
-@login_forbidden
-def password_reset_done(request):
- """The view telling the user that an email has been sent.
-
- :param request: A django request object.
- :type request: request
- """
- return django_password_reset_done(
- request,
- template_name='user_map/account/password_reset_done.html')
-
-
-@login_forbidden
-def password_reset_confirm(request, uidb64=None, token=None):
- """The view containing form to reset password and process it.
-
- :param request: A django request object.
- :type request: request
-
- :param uidb64: An unique ID of the user.
- :type uidb64: str
-
- :param token: Token to check if the reset password link is valid.
- :type token: str
- """
- return django_password_reset_confirm(
- request,
- uidb64=uidb64,
- token=token,
- template_name='user_map/account/password_reset_confirm.html',
- set_password_form=SetPasswordForm,
- post_reset_redirect=reverse('user_map:password_reset_complete'))
-
-
-@login_forbidden
-def password_reset_complete(request):
- """The view telling the user that reset password has been completed.
-
- :param request: A django request object.
- :type request: request
- """
- return django_password_reset_complete(
- request,
- template_name='user_map/account/password_reset_complete.html')
-
-
-def download(request):
- """The view to download users data as CSV.
-
- :param request: A django request object.
- :type request: request
-
- :return: A CSV file.
- :type: HttpResponse
- """
- response = HttpResponse(content_type='text/csv')
- response['Content-Disposition'] = 'attachment; filename="users.csv"'
-
- users = User.objects.filter(role__sort_number__gte=1)
- writer = csv.writer(response)
-
- fields = ['name', 'website', 'role', 'location']
- headers = ['No.']
- for field in fields:
- verbose_name_field = users.model._meta.get_field(field).verbose_name
- headers.append(verbose_name_field)
- writer.writerow(headers)
-
- for idx, user in enumerate(users):
- row = [idx + 1]
- for field in fields:
- field_value = getattr(user, field)
- row.append(field_value)
- writer.writerow(row)
-
- return response
+ return context
+
+
+class UserMapList(generics.ListAPIView):
+ """List of User Map with REST."""
+ queryset = UserMap.objects.filter(is_hidden=False)
+ serializer_class = UserMapSerializer
+
+
+class UserAddView(BaseTemplateMixin, CreateView):
+ """View to add user to the map."""
+ model = UserMap
+ form_class = UserMapForm
+ template_name = 'user_map/user_add_update.html'
+
+ def form_valid(self, form):
+ form.instance.user = self.request.user
+ messages.add_message(
+ self.request,
+ messages.INFO,
+ 'You have been added successfully to the map!'
+ )
+ return super(UserAddView, self).form_valid(form)
+
+ def get_success_url(self):
+ return reverse('user_map:index')
+
+ def dispatch(self, request, *args, **kwargs):
+ if self.is_mapped:
+ return HttpResponseRedirect(reverse('user_map:index'))
+ return super(UserAddView, self).dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super(UserAddView, self).get_context_data(**kwargs)
+ context['title'] = 'Add me to the map!'
+ context['description'] = ('Hey %s, please provide your information '
+ 'on the form below.') % self.request.user
+ return context
+
+
+class UserUpdateView(BaseTemplateMixin, UpdateView):
+ """View to update a user."""
+ model = UserMap
+ form_class = UserMapForm
+ template_name = 'user_map/user_add_update.html'
+
+ def get(self, request, **kwargs):
+ try:
+ self.object = self.request.user.usermap
+ except ObjectDoesNotExist:
+ return HttpResponseRedirect(reverse('user_map:index'))
+
+ form_class = self.get_form_class()
+ form = self.get_form(form_class)
+ context = self.get_context_data(object=self.object, form=form)
+ return self.render_to_response(context)
+
+ def get_object(self, queryset=None):
+ return self.request.user.usermap
+
+ def get_success_url(self):
+ return reverse('user_map:index')
+
+ def form_valid(self, form):
+ # form.instance.user = self.request.user
+ messages.add_message(
+ self.request,
+ messages.INFO,
+ 'You are successfully updated!'
+ )
+ return super(UserUpdateView, self).form_valid(form)
+
+ def get_context_data(self, **kwargs):
+ context = super(UserUpdateView, self).get_context_data(**kwargs)
+ context['title'] = 'Update Profile'
+ context['description'] = ('Hey %s, please change the information you '
+ 'want to update below!') % self.request.user
+ return context