Revision 890b0eaf

b/astakos/im/admin/forms.py
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
# 
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from django import forms
35
from django.utils.translation import ugettext as _
36
from django.contrib.auth.forms import UserCreationForm
37
from django.conf import settings
38
from hashlib import new as newhasher
39

  
40
from astakos.im.models import AstakosUser
41
from astakos.im.util import get_or_create_user
42

  
43
class AdminProfileForm(forms.ModelForm):
44
    """
45
    Subclass of ``ModelForm`` for permiting user to edit his/her profile.
46
    Most of the fields are readonly since the user is not allowed to change them.
47
    
48
    The class defines a save method which sets ``is_verified`` to True so as the user
49
    during the next login will not to be redirected to profile page.
50
    """
51
    class Meta:
52
        model = AstakosUser
53
    
54
    def __init__(self, *args, **kwargs):
55
        super(AdminProfileForm, self).__init__(*args, **kwargs)
56
        instance = getattr(self, 'instance', None)
57
        ro_fields = ('username','date_joined', 'auth_token', 'last_login', 'email')
58
        if instance and instance.id:
59
            for field in ro_fields:
60
                if isinstance(self.fields[field].widget, forms.CheckboxInput):
61
                    self.fields[field].widget.attrs['disabled'] = True
62
                self.fields[field].widget.attrs['readonly'] = True
b/astakos/im/admin/templates/pending_users.html
36 36
      <td>{{ user.email }}</td>
37 37
      <td>{{ user.inviter.realname }}</td>
38 38
      <td>
39
        <form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post">
39
        <form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post">{% csrf_token %}
40 40
        <input type="hidden" name="page" value="{{ page }}">
41 41
        <button type="submit" class="btn primary">Activate</button>
42 42
        </form>
b/astakos/im/admin/templates/users_create.html
2 2

  
3 3
{% block body %}
4 4

  
5
<form action="{% url astakos.im.admin.views.users_create %}" method="post">
5
<form action="{% url astakos.im.admin.views.users_create %}" method="post">{% csrf_token %}
6 6
  <div class="clearfix">
7 7
    <label for="user-username">Username</label>
8 8
    <div class="input">
b/astakos/im/admin/templates/users_info.html
4 4

  
5 5
{% block body %}
6 6

  
7
<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post">
8
  <div class="clearfix">
9
    <label for="user-id">ID</label>
10
    <div class="input">
11
      <span class="uneditable-input" id="user-id">{{ user.id }}</span>
12
    </div>
13
  </div>
14

  
15
  <div class="clearfix">
16
    <label for="user-username">Username</label>
17
    <div class="input">
18
      <input class="span4" id="user-username" name="username" value="{{ user.username }}" type="text" />
19
    </div>
20
  </div>
21

  
22
  <div class="clearfix">
23
    <label for="user-first-name">First Name</label>
24
    <div class="input">
25
      <input class="span4" id="user-first-name" name="first_name" value="{{ user.first_name }}" type="text" />
26
    </div>
27
  </div>
28
  
29
  <div class="clearfix">
30
    <label for="user-last-name">Last Name</label>
31
    <div class="input">
32
      <input class="span4" id="user-last-name" name="last_name" value="{{ user.last_name }}" type="text" />
33
    </div>
34
  </div>
35

  
36
  <div class="clearfix">
37
    <label for="user-admin">Admin</label>
38
    <div class="input">
39
      <ul class="inputs-list">
40
        <li>
41
          <label>
42
            <input type="checkbox" id="user-admin" name="admin"{% if user.is_superuser %} checked{% endif %}>
43
          </label>
44
        </li>
45
      </ul>
46
    </div>
47
  </div>
48

  
49
  <div class="clearfix">
50
    <label for="user-affiliation">Affiliation</label>
51
    <div class="input">
52
      <input class="span4" id="user-affiliation" name="affiliation" value="{{ user.affiliation }}" type="text" />
53
    </div>
54
  </div>
55

  
56
  <div class="clearfix">
57
    <label for="user-is-active">Is active?</label>
58
    <div class="input">
59
      <ul class="inputs-list">
60
        <li>
61
          <label>
62
            <input type="checkbox" id="user-is-active" name="is_active"{% if user.is_active %} checked{% endif %}>
63
          </label>
64
        </li>
65
      </ul>
66
    </div>
67
  </div>
68

  
69
  <div class="clearfix">
70
    <label for="user-invitations">Invitations</label>
71
    <div class="input">
72
      <input class="span2" id="user-invitations" name="invitations" value="{{ user.invitations }}" type="text" />
73
    </div>
74
  </div>
75

  
76
  <div class="clearfix">
77
    <label for="user-quota">Quota</label>
78
    <div class="input">
79
      <div class="input-append">
80
        <input class="span2" id="user-quota" name="quota" value="{{ user.quota|GiB }}" type="text" />
81
        <span class="add-on">GiB</span>
82
      </div>
83
    </div>
84
  </div>
85

  
86
  <div class="clearfix">
87
    <label for="user-token">Token</label>
88
    <div class="input">
89
      <input class="span4" id="user-token" name="auth_token" value="{{ user.auth_token }}" type="text" />
90
    </div>
91
  </div>
92

  
93
  <div class="clearfix">
94
    <label for="token-created">Token Created</label>
95
    <div class="input">
96
      <span class="uneditable-input" id="token-created">{{ user.auth_token_created }}</span>
97
    </div>
98
  </div>
99

  
100
  <div class="clearfix">
101
    <label for="token-expires">Token Expires</label>
102
    <div class="input">
103
      <input type="datetime" class="span4" id="token-expires" name="auth_token_expires" value="{{ user.auth_token_expires|isoformat }}" />
104
    </div>
105
  </div>
106

  
107
  <div class="clearfix">
108
    <label for="user-date-joined">Created</label>
109
    <div class="input">
110
      <span class="uneditable-input" id="user-date-joined">{{ user.date_joined }}</span>
111
    </div>
112
  </div>
113

  
114
  <div class="clearfix">
115
    <label for="user-updated">Updated</label>
116
    <div class="input">
117
      <span class="uneditable-input" id="user-updated">{{ user.updated }}</span>
118
    </div>
119
  </div>
7
<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post">{% csrf_token %}
8
  {{ form.as_p }}
120 9

  
121 10
  <div class="actions">
122 11
    <button type="submit" class="btn primary">Save Changes</button>
b/astakos/im/admin/templates/welcome_email.txt
1
--- A translation in English follows ---
2

  
3
Αγαπητέ/η {{ user.realname }},
4

  
5
Ο λογαρισμός σας για την υπηρεσία {{ site_name }} της ΕΔΕΤκατά την Alpha (πιλοτική)
6
φάση λειτουργίας της έχει ενεργοποιηθεί.
7

  
8
Για να συνδεθείτε, χρησιμοποιήστε τον παρακάτω σύνδεσμο:
9

  
10
{{ url }}
11

  
12
Σημείωση:
13

  
14
Η υπηρεσία θα είναι για μερικές εβδομάδες σε φάση λειτουργίας Alpha. Αν
15
και έχουμε κάνει ότι είναι δυνατό για να εξασφαλίσουμε την ποιότητα της
16
υπηρεσίας, δεν αποκλείεται να εμφανιστούν προβλήματα στο λογισμικό
17
διαχείρισης ή η υπηρεσία να μην είναι διαθέσιμη κατά διαστήματα. Για
18
αυτό το λόγο, σας παρακαλούμε να μη μεταφέρετε ακόμη σημαντικά κομμάτια
19
της δουλειάς σας στην υπηρεσία {{ site_name }}. Επίσης, παρακαλούμε να έχετε
20
υπόψη σας ότι όλα τα δεδομένα, θα διαγραφούν κατά τη μετάβαση από την
21
έκδοση Alpha στην έκδοση Beta. Θα υπάρξει έγκαιρη ειδοποίησή σας πριν
22
από τη μετάβαση αυτή.
23

  
24
Περισσότερα για την υπηρεσία θα βρείτε στο {{ baseurl }}, αφού
25
έχετε ενεργοποιήσει την πρόσκλησή σας.
26

  
27
Αναμένουμε τα σχόλια και τις παρατηρήσεις σας, για να βελτιώσουμε τη
28
λειτουργικότητα και την αξιοπιστία της καινοτομικής αυτής υπηρεσίας.
29

  
30
Για όποιες παρατηρήσεις ή προβλήματα στη λειτουργεία της υπηρεσίας μπορείτε να
31
απευθυνθείτε στο {{ support }}.
32

  
33
Σας ευχαριστούμε πολύ για τη συμμετοχή στην Alpha λειτουργία του {{ site_name }}.
34

  
35
Με εκτίμηση,
36

  
37
Εθνικό Δίκτυο Έρευνας και Τεχνολογίας (ΕΔΕΤ/GRNET)
38

  
39
--
40

  
41
Dear {{ user.realname }},
42

  
43
Your account for GRNET's {{ site_name }} service for its Alpha test phase has been
44
activated.
45

  
46
To login, please use the following link:
47

  
48
{{ url }}
49

  
50
Please note:
51

  
52
This service is, for a few weeks, in Alpha. Although every care has been
53
taken to ensure high quality, it is possible there may still be software
54
bugs, or periods of limited service availability. For this reason, we
55
kindly request you do not transfer important parts of your work to
56
{{ site_name }}, yet. Also, please bear in mind that all data, will be deleted
57
when the service moves to Beta test. Before the transition, you will be
58
notified in time.
59

  
60
For more information, please visit {{ baseurl }}, after
61
activating your invitation.
62

  
63
We look forward to your feedback, to improve the functionality and
64
reliability of this innovative service.
65

  
66
For any remarks or problems you can contact {{ support }}.
67

  
68
Thank you for participating in the Alpha test of {{ site_name }}.
69

  
70
Greek Research and Technonogy Network - GRNET
b/astakos/im/admin/views.py
53 53
from django.utils.http import urlencode
54 54
from django.utils.translation import ugettext as _
55 55
from django.core.urlresolvers import reverse
56
from django.contrib import messages
57
from django.db import transaction
58
from django.contrib.auth.models import AnonymousUser
59
from django.contrib.sites.models import get_current_site
56 60

  
57
#from astakos.im.openid_store import PithosOpenIDStore
58 61
from astakos.im.models import AstakosUser, Invitation
59 62
from astakos.im.util import isoformat, get_or_create_user, get_context
60 63
from astakos.im.forms import *
61 64
from astakos.im.backends import get_backend
62 65
from astakos.im.views import render_response, index
66
from astakos.im.admin.forms import AdminProfileForm
63 67

  
64 68
def requires_admin(func):
69
    """
70
    Decorator checkes whether the request.user is a superuser and if not
71
    redirects to login page.
72
    """
65 73
    @wraps(func)
66 74
    def wrapper(request, *args):
67 75
        if not settings.BYPASS_ADMIN_AUTH:
68
            if not request.user:
76
            if isinstance(request.user, AnonymousUser):
69 77
                next = urlencode({'next': request.build_absolute_uri()})
70 78
                login_uri = reverse(index) + '?' + next
71 79
                return HttpResponseRedirect(login_uri)
......
76 84

  
77 85
@requires_admin
78 86
def admin(request, template_name='admin.html', extra_context={}):
87
    """
88
    Renders the admin page
89
    
90
    If the ``request.user`` is not a superuser redirects to login page.
91
    
92
   **Arguments**
93
    
94
    ``template_name``
95
        A custom template to use. This is optional; if not specified,
96
        this will default to ``admin.html``.
97
    
98
    ``extra_context``
99
        An dictionary of variables to add to the template context.
100
    
101
   **Template:**
102
    
103
    admin.html or ``template_name`` keyword argument.
104
    
105
   **Template Context:**
106
    
107
    The template context is extended by:
108
    
109
    * tab: the name of the active tab
110
    * stats: dictionary containing the number of all and prending users
111
    """
79 112
    stats = {}
80 113
    stats['users'] = AstakosUser.objects.count()
81 114
    stats['pending'] = AstakosUser.objects.filter(is_active = False).count()
......
85 118
    stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
86 119
    
87 120
    kwargs = {'tab': 'home', 'stats': stats}
88
    context = get_context(request, extra_context, **kwargs)
121
    context = get_context(request, extra_context,**kwargs)
89 122
    return render_response(template_name, context_instance = context)
90 123

  
91 124
@requires_admin
92 125
def users_list(request, template_name='users_list.html', extra_context={}):
126
    """
127
    Displays the list of all users.
128
    
129
    If the ``request.user`` is not a superuser redirects to login page.
130
    
131
   **Arguments**
132
    
133
    ``template_name``
134
        A custom template to use. This is optional; if not specified,
135
        this will default to ``users_list.html``.
136
    
137
    ``extra_context``
138
        An dictionary of variables to add to the template context.
139
    
140
   **Template:**
141
    
142
    users_list.html or ``template_name`` keyword argument.
143
    
144
   **Template Context:**
145
    
146
    The template context is extended by:
147
    
148
    * users: list of users fitting in current page
149
    * filter: search key
150
    * pages: the number of pages
151
    * prev: the previous page
152
    * next: the current page
153
    
154
   **Settings:**
155
    
156
    * ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
157
    """
93 158
    users = AstakosUser.objects.order_by('id')
94 159
    
95 160
    filter = request.GET.get('filter', '')
......
115 180
              'pages':range(1, npages + 1),
116 181
              'prev':prev,
117 182
              'next':next}
118
    context = get_context(request, extra_context, **kwargs)
183
    context = get_context(request, extra_context,**kwargs)
119 184
    return render_response(template_name, context_instance = context)
120 185

  
121 186
@requires_admin
122 187
def users_info(request, user_id, template_name='users_info.html', extra_context={}):
188
    """
189
    Displays the specific user profile.
190
    
191
    If the ``request.user`` is not a superuser redirects to login page.
192
    
193
   **Arguments**
194
    
195
    ``template_name``
196
        A custom template to use. This is optional; if not specified,
197
        this will default to ``users_info.html``.
198
    
199
    ``extra_context``
200
        An dictionary of variables to add to the template context.
201
    
202
   **Template:**
203
    
204
    users_info.html or ``template_name`` keyword argument.
205
    
206
   **Template Context:**
207
    
208
    The template context is extended by:
209
    
210
    * user: the user instance identified by ``user_id`` keyword argument
211
    """
123 212
    if not extra_context:
124 213
        extra_context = {}
125
    kwargs = {'user':AstakosUser.objects.get(id=user_id)}
126
    context = get_context(request, extra_context, **kwargs)
127
    return render_response(template_name, context_instance = context)
214
    user = AstakosUser.objects.get(id=user_id)
215
    return render_response(template_name,
216
                           form = AdminProfileForm(instance=user),
217
                           context_instance = get_context(request, extra_context))
128 218

  
129 219
@requires_admin
130
def users_modify(request, user_id):
131
    user = AstakosUser.objects.get(id=user_id)
132
    user.username = request.POST.get('username')
133
    user.first_name = request.POST.get('first_name')
134
    user.last_name = request.POST.get('last_name')
135
    user.is_superuser = True if request.POST.get('admin') else False
136
    user.affiliation = request.POST.get('affiliation')
137
    user.is_active = True if request.POST.get('is_active') else False
138
    user.invitations = int(request.POST.get('invitations') or 0)
139
    #user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
140
    user.auth_token = request.POST.get('auth_token')
141
    try:
142
        auth_token_expires = request.POST.get('auth_token_expires')
143
        d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
144
        user.auth_token_expires = d
145
    except ValueError:
146
        pass
147
    user.save()
148
    return redirect(users_info, user.id)
220
def users_modify(request, user_id, template_name='users_info.html', extra_context={}):
221
    """
222
    Update the specific user information. Upon success redirects to ``user_info`` view.
223
    
224
    If the ``request.user`` is not a superuser redirects to login page.
225
    """
226
    form = AdminProfileForm(request.POST)
227
    if form.is_valid():
228
        form.save()
229
        return redirect(users_info, user.id, template_name, extra_context)
230
    return render_response(template_name,
231
                           form = form,
232
                           context_instance = get_context(request, extra_context))
149 233

  
150 234
@requires_admin
151 235
def users_delete(request, user_id):
236
    """
237
    Deletes the specified user
238
    
239
    If the ``request.user`` is not a superuser redirects to login page.
240
    """
152 241
    user = AstakosUser.objects.get(id=user_id)
153 242
    user.delete()
154 243
    return redirect(users_list)
155 244

  
156 245
@requires_admin
157 246
def pending_users(request, template_name='pending_users.html', extra_context={}):
247
    """
248
    Displays the list of the pending users.
249
    
250
    If the ``request.user`` is not a superuser redirects to login page.
251
    
252
   **Arguments**
253
    
254
    ``template_name``
255
        A custom template to use. This is optional; if not specified,
256
        this will default to ``users_list.html``.
257
    
258
    ``extra_context``
259
        An dictionary of variables to add to the template context.
260
    
261
   **Template:**
262
    
263
    pending_users.html or ``template_name`` keyword argument.
264
    
265
   **Template Context:**
266
    
267
    The template context is extended by:
268
    
269
    * users: list of pending users fitting in current page
270
    * filter: search key
271
    * pages: the number of pages
272
    * prev: the previous page
273
    * next: the current page
274
    
275
   **Settings:**
276
    
277
    * ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
278
    """
158 279
    users = AstakosUser.objects.order_by('id')
159 280
    
160 281
    users = users.filter(is_active = False)
......
183 304
              'prev':prev,
184 305
              'next':next}
185 306
    return render_response(template_name,
186
                            context_instance = get_context(request, extra_context, **kwargs))
307
                            context_instance = get_context(request, extra_context,**kwargs))
187 308

  
188
def _send_greeting(baseurl, user):
309
def _send_greeting(request, user, template_name):
189 310
    url = reverse('astakos.im.views.index')
190 311
    subject = _('Welcome to %s' %settings.SERVICE_NAME)
191
    message = render_to_string('welcome.txt', {
312
    site = get_current_site(request)
313
    baseurl = request.build_absolute_uri('/').rstrip('/')
314
    message = render_to_string(template_name, {
192 315
                'user': user,
193 316
                'url': url,
194 317
                'baseurl': baseurl,
195
                'service': settings.SERVICE_NAME,
318
                'site_name': site.name,
196 319
                'support': settings.DEFAULT_CONTACT_EMAIL})
197 320
    sender = settings.DEFAULT_FROM_EMAIL
198 321
    send_mail(subject, message, sender, [user.email])
199 322
    logging.info('Sent greeting %s', user)
200 323

  
201 324
@requires_admin
202
def users_activate(request, user_id, template_name='pending_users.html', extra_context={}):
325
@transaction.commit_manually
326
def users_activate(request, user_id, template_name='pending_users.html', extra_context={}, email_template_name='welcome_email.txt'):
327
    """
328
    Activates the specific user and sends an email. Upon success renders the
329
    ``template_name`` keyword argument if exists else renders ``pending_users.html``.
330
    
331
    If the ``request.user`` is not a superuser redirects to login page.
332
    
333
   **Arguments**
334
    
335
    ``template_name``
336
        A custom template to use. This is optional; if not specified,
337
        this will default to ``users_list.html``.
338
    
339
    ``extra_context``
340
        An dictionary of variables to add to the template context.
341
    
342
   **Templates:**
343
    
344
    pending_users.html or ``template_name`` keyword argument.
345
    welcome_email.txt or ``email_template_name`` keyword argument.
346
    
347
   **Template Context:**
348
    
349
    The template context is extended by:
350
    
351
    * users: list of pending users fitting in current page
352
    * filter: search key
353
    * pages: the number of pages
354
    * prev: the previous page
355
    * next: the current page
356
    """
203 357
    user = AstakosUser.objects.get(id=user_id)
204 358
    user.is_active = True
205
    status = 'success'
359
    user.save()
360
    status = messages.SUCCESS
206 361
    try:
207
        _send_greeting(request.build_absolute_uri('/').rstrip('/'), user)
362
        _send_greeting(request, user, email_template_name)
208 363
        message = _('Greeting sent to %s' % user.email)
209
        user.save()
364
        transaction.commit()
210 365
    except (SMTPException, socket.error) as e:
211
        status = 'error'
366
        status = messages.ERROR
212 367
        name = 'strerror'
213 368
        message = getattr(e, name) if hasattr(e, name) else e
369
        transaction.rollback()
370
    messages.add_message(request, status, message)
214 371
    
215 372
    users = AstakosUser.objects.order_by('id')
216 373
    users = users.filter(is_active = False)
......
230 387
              'pages':range(1, npages + 1),
231 388
              'page':page,
232 389
              'prev':prev,
233
              'next':next,
234
              'message':message}
390
              'next':next}
235 391
    return render_response(template_name,
236
                           context_instance = get_context(request, extra_context, **kwargs))
392
                           context_instance = get_context(request, extra_context,**kwargs))
237 393

  
238 394
@requires_admin
239 395
def invitations_list(request, template_name='invitations_list.html', extra_context={}):
396
    """
397
    Displays a list with the Invitations.
398
    
399
    If the ``request.user`` is not a superuser redirects to login page.
400
    
401
   **Arguments**
402
    
403
    ``template_name``
404
        A custom template to use. This is optional; if not specified,
405
        this will default to ``invitations_list.html``.
406
    
407
    ``extra_context``
408
        An dictionary of variables to add to the template context.
409
    
410
   **Templates:**
411
    
412
    invitations_list.html or ``template_name`` keyword argument.
413
    
414
   **Template Context:**
415
    
416
    The template context is extended by:
417
    
418
    * invitations: list of invitations fitting in current page
419
    * filter: search key
420
    * pages: the number of pages
421
    * prev: the previous page
422
    * next: the current page
423
    """
240 424
    invitations = Invitation.objects.order_by('id')
241 425
    
242 426
    filter = request.GET.get('filter', '')
......
263 447
              'prev':prev,
264 448
              'next':next}
265 449
    return render_response(template_name,
266
                           context_instance = get_context(request, extra_context, **kwargs))
450
                           context_instance = get_context(request, extra_context,**kwargs))
267 451

  
268 452
@requires_admin
269 453
def invitations_export(request):
454
    """
455
    Exports the invitation list in csv file.
456
    """
270 457
    # Create the HttpResponse object with the appropriate CSV header.
271 458
    response = HttpResponse(mimetype='text/csv')
272 459
    response['Content-Disposition'] = 'attachment; filename=invitations.csv'
......
299 486

  
300 487
@requires_admin
301 488
def users_export(request):
489
    """
490
    Exports the user list in csv file.
491
    """
302 492
    # Create the HttpResponse object with the appropriate CSV header.
303 493
    response = HttpResponse(mimetype='text/csv')
304 494
    response['Content-Disposition'] = 'attachment; filename=users.csv'
......
327 517

  
328 518
@requires_admin
329 519
def users_create(request, template_name='users_create.html', extra_context={}):
520
    """
521
    Creates a user. Upon success redirect to ``users_info`` view.
522
    
523
   **Arguments**
524
    
525
    ``template_name``
526
        A custom template to use. This is optional; if not specified,
527
        this will default to ``users_create.html``.
528
    
529
    ``extra_context``
530
        An dictionary of variables to add to the template context.
531
    
532
   **Templates:**
533
    
534
    users_create.html or ``template_name`` keyword argument.
535
    """
330 536
    if request.method == 'GET':
331 537
        return render_response(template_name,
332 538
                               context_instance=get_context(request, extra_context))
......
338 544
        user.last_name = request.POST.get('last_name')
339 545
        user.is_superuser = True if request.POST.get('admin') else False
340 546
        user.affiliation = request.POST.get('affiliation')
341
        user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
547
        user.quota = int(request.POST.get('quota') or 0) * (1024**3)  # In GiB
342 548
        user.renew_token()
343 549
        user.provider = 'local'
344 550
        user.save()
b/astakos/im/api.py
82 82
        
83 83
        response = HttpResponse()
84 84
        response.status=204
85
        user_info = user.__dict__
86
        for k,v in user_info.items():
87
            if isinstance(v,  datetime.datetime):
88
                user_info[k] = v.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
89
        user_info.pop('_state')
85
        user_info = {'uniq':user.username,
86
                     'auth_token':user.auth_token,
87
                     'auth_token_created':user.auth_token_created,
88
                     'auth_token_expires':user.auth_token_expires}
90 89
        response.content = json.dumps(user_info)
91 90
        update_response_headers(response)
92 91
        return response
b/astakos/im/auth_backends.py
5 5

  
6 6
from astakos.im.models import AstakosUser
7 7

  
8
class AstakosUserModelBackend(ModelBackend):
8
class AstakosUserModelCredentialsBackend(ModelBackend):
9 9
    def authenticate(self, username=None, password=None):
10 10
        try:
11 11
            user = AstakosUser.objects.get(username=username)
......
19 19
            return AstakosUser.objects.get(pk=user_id)
20 20
        except AstakosUser.DoesNotExist:
21 21
            return None
22

  
22
    
23 23
    #@property
24 24
    #def user_class(self):
25 25
    #    if not hasattr(self, '_user_class'):
......
28 28
    #        print '#', self._user_class
29 29
    #        if not self._user_class:
30 30
    #            raise ImproperlyConfigured('Could not get custom user model')
31
    #    return self._user_class
31
    #    return self._user_class
32

  
33
class AstakosUserModelTokenBackend(ModelBackend):
34
    def authenticate(self, username=None, auth_token=None):
35
        try:
36
            user = AstakosUser.objects.get(username=username)
37
            if user.auth_token == auth_token:
38
                return user
39
        except AstakosUser.DoesNotExist:
40
            return None
41

  
42
    def get_user(self, user_id):
43
        try:
44
            return AstakosUser.objects.get(pk=user_id)
45
        except AstakosUser.DoesNotExist:
46
            return None
b/astakos/im/backends/__init__.py
33 33

  
34 34
from django.conf import settings
35 35
from django.utils.importlib import import_module
36
from django.core.exceptions import ImproperlyConfigured
37
from django.core.mail import send_mail
38
from django.template.loader import render_to_string
39
from django.utils.translation import ugettext as _
40
from django.contrib.auth.forms import UserCreationForm
41
from django.contrib import messages
42

  
43
from smtplib import SMTPException
44
from urllib import quote
45

  
46
from astakos.im.util import get_or_create_user
47
from astakos.im.models import AstakosUser, Invitation
48
from astakos.im.forms import ExtendedUserCreationForm, InvitedExtendedUserCreationForm
36 49

  
37 50
def get_backend():
38 51
    """
39 52
    Return an instance of a registration backend,
40
    according to the INVITATIONS_ENABLED setting.
41

  
53
    according to the INVITATIONS_ENABLED setting
54
    (if True returns ``astakos.im.backends.InvitationsBackend`` and if False
55
    returns ``astakos.im.backends.SimpleBackend``).
56
    
57
    If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
58
    is raised.
42 59
    """
43
    module = 'invitations' if settings.INVITATIONS_ENABLED else 'simple'
44
    module = 'astakos.im.backends.%s' %module
45
    backend_class_name = 'Backend'
60
    module = 'astakos.im.backends'
61
    prefix = 'Invitations' if settings.INVITATIONS_ENABLED else 'Simple'
62
    backend_class_name = '%sBackend' %prefix
46 63
    try:
47 64
        mod = import_module(module)
48 65
    except ImportError, e:
......
51 68
        backend_class = getattr(mod, backend_class_name)
52 69
    except AttributeError:
53 70
        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
54
    return backend_class()
71
    return backend_class()
72

  
73
class InvitationsBackend(object):
74
    """
75
    A registration backend which implements the following workflow: a user
76
    supplies the necessary registation information, if the request contains a valid
77
    inivation code the user is automatically activated otherwise an inactive user
78
    account is created and the user is going to receive an email as soon as an
79
    administrator activates his/her account.
80
    """
81
    def get_signup_form(self, request):
82
        """
83
        Returns the necassary registration form depending the user is invited or not
84
        
85
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
86
        """
87
        code = request.GET.get('code', '')
88
        formclass = 'ExtendedUserCreationForm'
89
        if request.method == 'GET':
90
            initial_data = None
91
            if code:
92
                formclass = 'Invited%s' %formclass
93
                self.invitation = Invitation.objects.get(code=code)
94
                if self.invitation.is_consumed:
95
                    return HttpResponseBadRequest('Invitation has beeen used')
96
                initial_data.update({'username':self.invitation.username,
97
                                       'email':self.invitation.username,
98
                                       'realname':self.invitation.realname})
99
                inviter = AstakosUser.objects.get(username=self.invitation.inviter)
100
                initial_data['inviter'] = inviter.realname
101
        else:
102
            initial_data = request.POST
103
        self.form = globals()[formclass](initial_data)
104
        return self.form
105
    
106
    def _is_preaccepted(self, user):
107
        """
108
        If there is a valid, not-consumed invitation code for the specific user
109
        returns True else returns False.
110
        
111
        It should be called after ``get_signup_form`` which sets invitation if exists.
112
        """
113
        invitation = getattr(self, 'invitation') if hasattr(self, 'invitation') else None
114
        if not invitation:
115
            return False
116
        if invitation.username == user.username and not invitation.is_consumed:
117
            return True
118
        return False
119
    
120
    def signup(self, request):
121
        """
122
        Creates a incative user account. If the user is preaccepted (has a valid
123
        invitation code) the user is activated and if the request param ``next``
124
        is present redirects to it.
125
        In any other case the method returns the action status and a message.
126
        """
127
        kwargs = {}
128
        form = self.form
129
        user = form.save(commit=False)
130
        
131
        try:
132
            if self._is_preaccepted(user):
133
                user.is_active = True
134
                message = _('Registration completed. You can now login.')
135
                next = request.POST.get('next')
136
                if next:
137
                    return redirect(next)
138
            else:
139
                message = _('Registration completed. You will receive an email upon your account\'s activation')
140
            status = messages.SUCCESS
141
        except Invitation.DoesNotExist, e:
142
            status = messages.ERROR
143
            message = _('Invalid invitation code')
144
        return status, message
145

  
146
class SimpleBackend(object):
147
    """
148
    A registration backend which implements the following workflow: a user
149
    supplies the necessary registation information, an incative user account is
150
    created and receives an email in order to activate his/her account.
151
    """
152
    def get_signup_form(self, request):
153
        """
154
        Returns the UserCreationForm
155
        """
156
        initial_data = request.POST if request.method == 'POST' else None
157
        return UserCreationForm(initial_data)
158
    
159
    def signup(self, request, email_template_name='activation_email.txt'):
160
        """
161
        Creates an inactive user account and sends a verification email.
162
        
163
        ** Arguments **
164
        
165
        ``email_template_name``
166
            A custom template for the verification email body to use. This is
167
            optional; if not specified, this will default to
168
            ``activation_email.txt``.
169
        
170
        ** Templates **
171
            activation_email.txt or ``email_template_name`` keyword argument
172
        
173
        ** Settings **
174
        
175
        * ACTIVATION_LOGIN_TARGET: Where users should activate their local account
176
        * DEFAULT_CONTACT_EMAIL: service support email
177
        * DEFAULT_FROM_EMAIL: from email
178
        """
179
        kwargs = {}
180
        form = self.form
181
        user = form.save(commit=False)
182
        status = messages.SUCCESS
183
        try:
184
            _send_verification(request, user, email_template_name)
185
            message = _('Verification sent to %s' % user.email)
186
        except (SMTPException, socket.error) as e:
187
            status = messages.ERROR
188
            name = 'strerror'
189
            message = getattr(e, name) if hasattr(e, name) else e
190
        return status, message
191

  
192
    def _send_verification(request, user, template_name):
193
        site = get_current_site(request)
194
        baseurl = request.build_absolute_uri('/').rstrip('/')
195
        url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
196
                                                  quote(user.auth_token),
197
                                                  quote(baseurl))
198
        message = render_to_string(template_name, {
199
                'user': user,
200
                'url': url,
201
                'baseurl': baseurl,
202
                'site_name': site.name,
203
                'support': settings.DEFAULT_CONTACT_EMAIL})
204
        sender = settings.DEFAULT_FROM_EMAIL
205
        send_mail('Pithos account activation', message, sender, [user.email])
206
        logging.info('Sent activation %s', user)
/dev/null
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from astakos.im.forms import InvitedLocalRegisterForm, LocalRegisterForm
35
from astakos.im.models import AstakosUser, Invitation
36

  
37
class Backend(object):
38
    def get_signup_form(self, request):
39
        code = request.GET.get('code', '')
40
        formclass = 'LocalRegisterForm'
41
        if request.method == 'GET':
42
            initial_data = None
43
            if code:
44
                formclass = 'InvitedLocalRegiterForm'
45
                invitation = Invitation.objects.get(code=code)
46
                if invitation.is_consumed:
47
                    return HttpResponseBadRequest('Invitation has beeen used')
48
                initial_data.update({'username':invitation.username,
49
                                       'email':invitation.username,
50
                                       'realname':invitation.realname})
51
                inviter = AstakosUser.objects.get(username=invitation.inviter)
52
                initial_data['inviter'] = inviter.realname
53
        else:
54
            initial_data = request.POST
55
        return globals()[formclass](initial_data)
56
    
57
    def is_preaccepted(user, code):
58
        invitation = self.invitation
59
        if invitation and not invitation.is_consumed and invitation.code == code:
60
            return True
61
        return False
62
    
63
    def signup(self, request, form):
64
        kwargs = {}
65
        for field in form.fields:
66
            if hasattr(AstakosUser(), field):
67
                kwargs[field] = form.cleaned_data[field]
68
        user = get_or_create_user(**kwargs)
69
        
70
        code = request.POST.get('code')
71
        if is_preaccepted(user, code):
72
            user.is_active = True
73
            user.save()
74
            message = _('Registration completed. You can now login.')
75
            next = request.POST.get('next')
76
            if next:
77
                return redirect(next)
78
        else:
79
            message = _('Registration completed. You will receive an email upon your account\'s activation')
80
        status = 'success'
81
        return status, message
/dev/null
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33
import socket
34
import logging
35

  
36
from django.core.mail import send_mail
37
from django.conf import settings
38
from django.template.loader import render_to_string
39
from django.utils.translation import ugettext as _
40
from smtplib import SMTPException
41
from urllib import quote
42

  
43
from astakos.im.forms import LocalRegisterForm
44
from astakos.im.util import get_or_create_user
45
from astakos.im.models import AstakosUser
46

  
47
class Backend(object):
48
    def get_signup_form(self, request):
49
        initial_data = request.POST if request.method == 'POST' else None
50
        return LocalRegisterForm(initial_data)
51
    
52
    def signup(self, request, form, success_url):
53
        kwargs = {}
54
        for field in form.fields:
55
            if hasattr(AstakosUser(), field):
56
                kwargs[field] = form.cleaned_data[field]
57
        user = get_or_create_user(**kwargs)
58
        
59
        status = 'success'
60
        try:
61
            send_verification(request.build_absolute_uri('/').rstrip('/'), user)
62
            message = _('Verification sent to %s' % user.email)
63
        except (SMTPException, socket.error) as e:
64
            status = 'error'
65
            name = 'strerror'
66
            message = getattr(e, name) if hasattr(e, name) else e
67
        
68
        if user and status == 'error':
69
            #delete created user
70
            user.delete()
71
        return status, message
72

  
73
def send_verification(baseurl, user):
74
    url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
75
                                              quote(user.auth_token),
76
                                              quote(baseurl))
77
    message = render_to_string('activation.txt', {
78
            'user': user,
79
            'url': url,
80
            'baseurl': baseurl,
81
            'service': settings.SERVICE_NAME,
82
            'support': settings.DEFAULT_CONTACT_EMAIL})
83
    sender = settings.DEFAULT_FROM_EMAIL
84
    send_mail('Pithos account activation', message, sender, [user.email])
85
    logging.info('Sent activation %s', user)
b/astakos/im/context_processors.py
41 41

  
42 42
def code(request):
43 43
    return {'code' : request.GET.get('code', '')}
44

  
45
def invitations(request):
46
    return {'invitations_enabled' :settings.INVITATIONS_ENABLED}
44 47
    
b/astakos/im/fixtures/auth_test_data.json
1 1
[
2 2
    {
3 3
        "model": "im.AstakosUser",
4
        "pk": 1,
4
        "pk": 2,
5 5
        "fields": {
6 6
            "username": "test",
7 7
            "level": 0,
......
15 15
    },
16 16
    {
17 17
        "model": "im.AstakosUser",
18
        "pk": 2,
18
        "pk": 3,
19 19
        "fields": {
20 20
            "username": "verigak",
21 21
            "level": 1,
......
30 30
    },
31 31
    {
32 32
        "model": "im.AstakosUser",
33
        "pk": 3,
33
        "pk": 4,
34 34
        "fields": {
35 35
            "username": "chazapis",
36 36
            "level": 1,
......
44 44
    },
45 45
    {
46 46
        "model": "im.AstakosUser",
47
        "pk": 4,
47
        "pk": 5,
48 48
        "fields": {
49 49
            "username": "gtsouk",
50 50
            "level": 1,
......
58 58
    },
59 59
    {
60 60
        "model": "im.AstakosUser",
61
        "pk": 5,
61
        "pk": 6,
62 62
        "fields": {
63 63
            "username": "papagian",
64 64
            "level": 1,
......
72 72
    },
73 73
    {
74 74
        "model": "im.AstakosUser",
75
        "pk": 6,
75
        "pk": 7,
76 76
        "fields": {
77 77
            "username": "louridas",
78 78
            "level": 1,
......
86 86
    },
87 87
    {
88 88
        "model": "im.AstakosUser",
89
        "pk": 7,
89
        "pk": 8,
90 90
        "fields": {
91 91
            "username": "chstath",
92 92
            "level": 1,
......
100 100
    },
101 101
    {
102 102
        "model": "im.AstakosUser",
103
        "pk": 8,
103
        "pk": 9,
104 104
        "fields": {
105 105
            "username": "pkanavos",
106 106
            "level": 1,
......
114 114
    },
115 115
    {
116 116
        "model": "im.AstakosUser",
117
        "pk": 9,
117
        "pk": 10,
118 118
        "fields": {
119 119
            "username": "mvasilak",
120 120
            "level": 1,
......
128 128
    },
129 129
    {
130 130
        "model": "im.AstakosUser",
131
        "pk": 10,
131
        "pk": 11,
132 132
        "fields": {
133 133
            "username": "διογένης",
134 134
            "level": 2,
b/astakos/im/forms.py
33 33

  
34 34
from django import forms
35 35
from django.utils.translation import ugettext as _
36
from django.contrib.auth.forms import UserCreationForm
36 37
from django.conf import settings
37 38
from hashlib import new as newhasher
38 39

  
39 40
from astakos.im.models import AstakosUser
41
from astakos.im.util import get_or_create_user
40 42

  
41
class RegisterForm(forms.Form):
42
    username = forms.CharField(widget=forms.widgets.TextInput())
43
    email = forms.EmailField(widget=forms.TextInput(),
44
                             label=_('Email address'))
45
    first_name = forms.CharField(widget=forms.TextInput(),
46
                                label=u'First Name', required=False)
47
    last_name = forms.CharField(widget=forms.TextInput(),
48
                                label=u'Last Name', required=False)
49
    
50
    def __init__(self, *args, **kwargs):
51
        super(forms.Form, self).__init__(*args, **kwargs)
52
    
53
    def clean_username(self):
54
        """
55
        Validate that the username is alphanumeric and is not already
56
        in use.
57
        
58
        """
43
class UniqueUserEmailField(forms.EmailField):
44
    """
45
    An EmailField which only is valid if no User has that email.
46
    """
47
    def validate(self, value):
48
        super(forms.EmailField, self).validate(value)
59 49
        try:
60
            user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username'])
50
            AstakosUser.objects.get(email = value)
51
            raise forms.ValidationError("Email already exists")
52
        except AstakosUser.MultipleObjectsReturned:
53
            raise forms.ValidationError("Email already exists")
61 54
        except AstakosUser.DoesNotExist:
62
            return self.cleaned_data['username']
63
        raise forms.ValidationError(_("A user with that username already exists."))
55
            pass
64 56

  
65
class LocalRegisterForm(RegisterForm):
66
    """ local signup form"""
67
    password = forms.CharField(widget=forms.PasswordInput(render_value=False),
68
                                label=_('Password'))
69
    password2 = forms.CharField(widget=forms.PasswordInput(render_value=False),
70
                                label=_('Confirm Password'))
57
class ExtendedUserCreationForm(UserCreationForm):
58
    """
59
    Extends the built in UserCreationForm in several ways:
71 60
    
72
    def __init__(self, *args, **kwargs):
73
        super(LocalRegisterForm, self).__init__(*args, **kwargs)
61
    * Adds an email field, which uses the custom UniqueUserEmailField,
62
      that is, the form does not validate if the email address already exists
63
      in the User table.
64
    * The username field is generated based on the email, and isn't visible.
65
    * first_name and last_name fields are added.
66
    * Data not saved by the default behavior of UserCreationForm is saved.
67
    """
68
    
69
    username = forms.CharField(required = False, max_length = 30)
70
    email = UniqueUserEmailField(required = True, label = 'Email address')
71
    first_name = forms.CharField(required = False, max_length = 30)
72
    last_name = forms.CharField(required = False, max_length = 30)
74 73
    
75
    def clean_username(self):
74
    def __init__(self, *args, **kwargs):
76 75
        """
77
        Validate that the username is alphanumeric and is not already
78
        in use.
79
        
76
        Changes the order of fields, and removes the username field.
80 77
        """
81
        try:
82
            user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username'])
83
        except AstakosUser.DoesNotExist:
84
            return self.cleaned_data['username']
85
        raise forms.ValidationError(_("A user with that username already exists."))
78
        super(UserCreationForm, self).__init__(*args, **kwargs)
79
        self.fields.keyOrder = ['email', 'first_name', 'last_name',
80
                                'password1', 'password2']
86 81
    
87
    def clean(self):
82
    def clean(self, *args, **kwargs):
83
        """
84
        Normal cleanup + username generation.
88 85
        """
89
        Verifiy that the values entered into the two password fields
90
        match. Note that an error here will end up in
91
        ``non_field_errors()`` because it doesn't apply to a single
92
        field.
86
        cleaned_data = super(UserCreationForm, self).clean(*args, **kwargs)
87
        if cleaned_data.has_key('email'):
88
            #cleaned_data['username'] = self.__generate_username(
89
            #                                            cleaned_data['email'])
90
            cleaned_data['username'] = cleaned_data['email']
91
        return cleaned_data
93 92
        
93
    def save(self, commit=True):
94 94
        """
95
        if 'password' in self.cleaned_data and 'password2' in self.cleaned_data:
96
            if self.cleaned_data['password'] != self.cleaned_data['password2']:
97
                raise forms.ValidationError(_("The two password fields didn't match."))
98
        return self.cleaned_data
95
        Saves the email, first_name and last_name properties, after the normal
96
        save behavior is complete.
97
        """
98
        user = super(UserCreationForm, self).save(commit)
99
        if user:
100
            kwargs = {}
101
            for field in self.fields:
102
                if hasattr(AstakosUser(), field):
103
                    kwargs[field] = self.cleaned_data[field]
104
            user = get_or_create_user(username=self.cleaned_data['email'], **kwargs)
105
        return user
99 106

  
100
class InvitedRegisterForm(RegisterForm):
107
class InvitedExtendedUserCreationForm(ExtendedUserCreationForm):
108
    """
109
    Subclass of ``RegistrationForm`` for registring a invited user. Adds a
110
    readonly field for inviter's name. The email is also readonly since
111
    it will be the invitation username.
112
    """
101 113
    inviter = forms.CharField(widget=forms.TextInput(),
102 114
                                label=_('Inviter Real Name'))
103 115
    
......
105 117
        super(RegisterForm, self).__init__(*args, **kwargs)
106 118
        
107 119
        #set readonly form fields
108
        self.fields['username'].widget.attrs['readonly'] = True
109 120
        self.fields['inviter'].widget.attrs['readonly'] = True
110 121

  
111
class InvitedLocalRegisterForm(LocalRegisterForm, InvitedRegisterForm):
112
    pass
122
class ProfileForm(forms.ModelForm):
123
    """
124
    Subclass of ``ModelForm`` for permiting user to edit his/her profile.
125
    Most of the fields are readonly since the user is not allowed to change them.
126
    
127
    The class defines a save method which sets ``is_verified`` to True so as the user
128
    during the next login will not to be redirected to profile page.
129
    """
130
    class Meta:
131
        model = AstakosUser
132
        exclude = ('groups', 'user_permissions')
133
    
134
    def __init__(self, *args, **kwargs):
135
        super(ProfileForm, self).__init__(*args, **kwargs)
136
        instance = getattr(self, 'instance', None)
137
        ro_fields = ('username','date_joined', 'updated', 'auth_token',
138
                     'auth_token_created', 'auth_token_expires', 'invitations',
139
                     'level', 'last_login', 'email', 'is_active', 'is_superuser',
140
                     'is_staff')
141
        if instance and instance.id:
142
            for field in ro_fields:
143
                if isinstance(self.fields[field].widget, forms.CheckboxInput):
144
                    self.fields[field].widget.attrs['disabled'] = True
145
                self.fields[field].widget.attrs['readonly'] = True
146
    
147
    def save(self, commit=True):
148
        user = super(ProfileForm, self).save(commit=False)
149
        user.is_verified = True
150
        if commit:
151
            user.save()
152
        return user
153

  
113 154

  
114
class LoginForm(forms.Form):
115
    username = forms.CharField(widget=forms.widgets.TextInput())
116
    password = forms.CharField(widget=forms.PasswordInput(render_value=False),
117
                                label=_('Password'))
155
class FeedbackForm(forms.Form):
156
    """
157
    Form for writing feedback.
158
    """
159
    feedback_msg = forms.CharField(widget=forms.Textarea(),
160
                                label=u'Message', required=False)
161
    feedback_data = forms.CharField(widget=forms.Textarea(),
162
                                label=u'Data', required=False)
163
    
b/astakos/im/models.py
45 45
from astakos.im.interface import get_quota, set_quota
46 46

  
47 47
class AstakosUser(User):
48
    """
49
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
50
    """
48 51
    # Use UserManager to get the create_user method, etc.
49 52
    objects = UserManager()
50 53
    
......
61 64
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
62 65
    
63 66
    updated = models.DateTimeField('Update date')
67
    is_verified = models.BooleanField('Is verified?', default=False)
64 68
    
65 69
    @property
66 70
    def realname(self):
......
117 121
        return self.username
118 122

  
119 123
class Invitation(models.Model):
124
    """
125
    Model for registring invitations
126
    """
120 127
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
121 128
                                null=True)
122 129
    realname = models.CharField('Real name', max_length=255)
b/astakos/im/target/invitation.py
37 37

  
38 38
from django.conf import settings
39 39
from django.http import HttpResponseBadRequest
40
from django.contrib.auth import authenticate
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff