bug 574271, Update collections
This commit is contained in:
Родитель
c80d66d302
Коммит
117f3875a3
|
@ -45,7 +45,8 @@ def check_ownership(request, obj, require_owner=False):
|
|||
def check_collection_ownership(request, collection, require_owner=False):
|
||||
if not request.user.is_authenticated():
|
||||
return False
|
||||
if not require_owner and action_allowed(request, 'Admin', '%'):
|
||||
|
||||
if action_allowed(request, 'Admin', '%'):
|
||||
return True
|
||||
elif request.user.id == collection.author_id:
|
||||
return True
|
||||
|
|
|
@ -6,25 +6,107 @@ from django.conf import settings
|
|||
import commonware
|
||||
from tower import ugettext as _
|
||||
|
||||
import amo
|
||||
from addons.models import Addon
|
||||
from .models import Collection, CollectionAddon
|
||||
from translations.widgets import TranslationTextInput, TranslationTextarea
|
||||
from users.models import UserProfile
|
||||
from .models import Collection, CollectionAddon, CollectionUser
|
||||
from . import tasks
|
||||
|
||||
privacy_choices = (
|
||||
(False, _('Only I can view this collection.')),
|
||||
(True, _('Anybody can view this collection.')))
|
||||
|
||||
log = commonware.log.getLogger('z.image')
|
||||
apps = ((a.id, a.pretty) for a in amo.APP_USAGE)
|
||||
collection_types = ((k, v) for k, v in amo.COLLECTION_CHOICES.iteritems()
|
||||
if k not in (amo.COLLECTION_ANONYMOUS, amo.COLLECTION_RECOMMENDED))
|
||||
|
||||
|
||||
class AdminForm(forms.Form):
|
||||
application = forms.TypedChoiceField(choices=apps, required=False,
|
||||
coerce=int)
|
||||
type = forms.TypedChoiceField(choices=collection_types, required=False,
|
||||
coerce=int)
|
||||
|
||||
def save(self, collection):
|
||||
collection.type = self.cleaned_data['type']
|
||||
collection.application_id = self.cleaned_data['application']
|
||||
collection.save()
|
||||
|
||||
|
||||
class AddonsForm(forms.Form):
|
||||
"""This form is related to adding addons to a collection."""
|
||||
|
||||
addon = forms.CharField(widget=forms.MultipleHiddenInput, required=False)
|
||||
addon_comment = forms.CharField(widget=forms.MultipleHiddenInput,
|
||||
required=False)
|
||||
def clean_addon(self):
|
||||
return self.data.getlist('addon')
|
||||
|
||||
def clean_addon_comment(self):
|
||||
addon_ids = self.data.getlist('addon')
|
||||
return dict(zip(map(int, addon_ids),
|
||||
self.data.getlist('addon_comment')))
|
||||
|
||||
def save(self, collection):
|
||||
collection.set_addons(self.cleaned_data['addon'],
|
||||
self.cleaned_data['addon_comment'])
|
||||
|
||||
|
||||
class ContributorsForm(forms.Form):
|
||||
"""This form is related to adding contributors to a collection."""
|
||||
|
||||
contributor = forms.CharField(widget=forms.MultipleHiddenInput,
|
||||
required=False)
|
||||
|
||||
new_owner = forms.IntegerField(widget=forms.HiddenInput, required=False)
|
||||
|
||||
def clean_new_owner(self):
|
||||
new_owner = self.cleaned_data['new_owner']
|
||||
if new_owner:
|
||||
return UserProfile.objects.get(pk=new_owner)
|
||||
|
||||
def clean_contributor(self):
|
||||
contributor_ids = self.data.getlist('contributor')
|
||||
return UserProfile.objects.filter(pk__in=contributor_ids)
|
||||
|
||||
def save(self, collection):
|
||||
collection.collectionuser_set.all().delete()
|
||||
for user in self.cleaned_data['contributor']:
|
||||
CollectionUser(collection=collection, user=user).save()
|
||||
|
||||
new_owner = self.cleaned_data['new_owner']
|
||||
|
||||
if new_owner:
|
||||
old_owner = collection.author
|
||||
collection.author = new_owner
|
||||
|
||||
cu, created = CollectionUser.objects.get_or_create(
|
||||
collection=collection, user=old_owner)
|
||||
if created:
|
||||
cu.save()
|
||||
|
||||
# Check for duplicate slugs.
|
||||
slug = collection.slug
|
||||
while new_owner.collections.filter(slug=slug).count():
|
||||
slug = slug + '-'
|
||||
collection.slug = slug
|
||||
collection.save()
|
||||
# New owner is no longer a contributor.
|
||||
collection.collectionuser_set.filter(user=new_owner).delete()
|
||||
|
||||
|
||||
class CollectionForm(forms.ModelForm):
|
||||
|
||||
name = forms.CharField(max_length=100,
|
||||
label=_('Give your collection a name.'))
|
||||
name = forms.CharField(
|
||||
label=_('Give your collection a name.'),
|
||||
widget=TranslationTextInput,
|
||||
)
|
||||
slug = forms.CharField(label=_('URL:'))
|
||||
description = forms.CharField(label=_('Describe your collections.'),
|
||||
widget=forms.Textarea(attrs={'rows': 3}),
|
||||
required=False)
|
||||
description = forms.CharField(
|
||||
label=_('Describe your collections.'),
|
||||
widget=TranslationTextarea,
|
||||
required=False)
|
||||
listed = forms.ChoiceField(
|
||||
label=_('Who can view your collection?'),
|
||||
widget=forms.RadioSelect,
|
||||
|
@ -35,19 +117,6 @@ class CollectionForm(forms.ModelForm):
|
|||
icon = forms.FileField(label=_('Give your collection an icon.'),
|
||||
required=False)
|
||||
|
||||
addon = forms.CharField(widget=forms.MultipleHiddenInput, required=False)
|
||||
addon_comment = forms.CharField(widget=forms.MultipleHiddenInput,
|
||||
required=False)
|
||||
|
||||
def clean_addon(self):
|
||||
addon_ids = self.data.getlist('addon')
|
||||
return Addon.objects.filter(pk__in=addon_ids)
|
||||
|
||||
def clean_addon_comment(self):
|
||||
addon_ids = self.data.getlist('addon')
|
||||
return dict(zip(map(int, addon_ids),
|
||||
self.data.getlist('addon_comment')))
|
||||
|
||||
def clean_description(self):
|
||||
description = self.cleaned_data['description']
|
||||
if description.strip() == '':
|
||||
|
@ -58,6 +127,8 @@ class CollectionForm(forms.ModelForm):
|
|||
def clean_slug(self):
|
||||
author = self.initial['author']
|
||||
slug = self.cleaned_data['slug']
|
||||
if self.instance and self.instance.slug == slug:
|
||||
return slug
|
||||
|
||||
if author.collections.filter(slug=slug).count():
|
||||
raise forms.ValidationError(
|
||||
|
@ -107,15 +178,7 @@ class CollectionForm(forms.ModelForm):
|
|||
fh.close()
|
||||
# XXX
|
||||
# tasks.resize_icon.delay(tmp_destination, destination)
|
||||
|
||||
for addon in self.cleaned_data['addon']:
|
||||
ca = CollectionAddon(collection=c, addon=addon)
|
||||
comment = self.cleaned_data['addon_comment'].get(addon.id)
|
||||
if comment:
|
||||
ca.comments = comment
|
||||
|
||||
ca.save()
|
||||
c.save() # Update counts, etc.
|
||||
tasks.resize_icon.delay(tmp_destination, destination)
|
||||
|
||||
return c
|
||||
|
||||
|
|
|
@ -178,7 +178,7 @@ class Collection(amo.models.ModelBase):
|
|||
self.save()
|
||||
return r
|
||||
|
||||
def set_addons(self, addon_ids):
|
||||
def set_addons(self, addon_ids, comments={}):
|
||||
"""Replace the current add-ons with a new list of add-on ids."""
|
||||
order = dict((a, idx) for idx, a in enumerate(addon_ids))
|
||||
|
||||
|
@ -207,6 +207,14 @@ class Collection(amo.models.ModelBase):
|
|||
for addon, ordering in update:
|
||||
(CollectionAddon.objects.filter(collection=self.id, addon=addon)
|
||||
.update(ordering=ordering, modified=now))
|
||||
|
||||
for addon, comment in comments.iteritems():
|
||||
c = CollectionAddon.objects.filter(collection=self.id, addon=addon)
|
||||
|
||||
if c:
|
||||
c[0].comments = comment
|
||||
c[0].save()
|
||||
|
||||
self.save()
|
||||
|
||||
def is_subscribed(self, user):
|
||||
|
@ -230,6 +238,12 @@ class Collection(amo.models.ModelBase):
|
|||
CollectionAddon.objects.filter(addon=addon, collection=self).delete()
|
||||
self.save() # To invalidate Collection.
|
||||
|
||||
def owned_by(self, user):
|
||||
return (user.id == self.author_id)
|
||||
|
||||
def publishable_by(self, user):
|
||||
return (user in self.users.all())
|
||||
|
||||
@staticmethod
|
||||
def transformer(collections):
|
||||
if not collections:
|
||||
|
@ -240,12 +254,6 @@ class Collection(amo.models.ModelBase):
|
|||
for c in collections:
|
||||
c.author = authors.get(c.author_id)
|
||||
|
||||
def owned_by(self, user):
|
||||
return (user.id == self.author_id)
|
||||
|
||||
def publishable_by(self, user):
|
||||
return (user in self.users.all())
|
||||
|
||||
|
||||
class CollectionAddon(amo.models.ModelBase):
|
||||
addon = models.ForeignKey(Addon)
|
||||
|
@ -313,7 +321,7 @@ class CollectionPromo(amo.models.ModelBase):
|
|||
promo_dict[promo_id].collection = collection.next()
|
||||
|
||||
|
||||
class CollectionRecommendation(amo.models.ModelBase):
|
||||
class CollectionRecommendation(models.Model):
|
||||
collection = models.ForeignKey(Collection, null=True,
|
||||
related_name="collection_one")
|
||||
other_collection = models.ForeignKey(Collection, null=True,
|
||||
|
|
|
@ -11,95 +11,15 @@
|
|||
<h2>{{ _('Create a New Collection') }}</h2>
|
||||
</header>
|
||||
|
||||
{% if form.errors %}
|
||||
<p class="error">
|
||||
{{ _('There are errors in this form. Please correct them below.') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% include 'bandwagon/includes/addedit_errors.html' %}
|
||||
|
||||
<div>
|
||||
<form method="post" action="{{ url('collections.add') }}"
|
||||
enctype="multipart/form-data">
|
||||
{{ csrf() }}
|
||||
{% include 'bandwagon/includes/addedit.html' %}
|
||||
{% include 'bandwagon/includes/addon_selector.html' %}
|
||||
|
||||
<h3>{{ _('Collection Description') }}</h3>
|
||||
<fieldset>
|
||||
<p>
|
||||
{{ form.errors['name']|safe }}
|
||||
{{ form.name.label|safe }}
|
||||
{{ form.name|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.errors['slug']|safe }}
|
||||
{{ form.slug.label|safe }}
|
||||
{{ url('collections.user', user.get_profile().nickname)|absolutify -}}
|
||||
{{ form.slug|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.description.label|safe }} {{ _('(optional)') }}
|
||||
{{ form.description|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.listed.label|safe }}
|
||||
</p>
|
||||
{{ form.listed|safe }}
|
||||
<p>
|
||||
{{ form.errors['icon']|safe }}
|
||||
{{ form.icon.label|safe }} {{ _('(optional)') }}
|
||||
{{ form.icon|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ _('PNG and JPG supported. Image will be resized to 32x32.') }}
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
<h3>{{ _('Add-ons in Your Collection') }}</h3>
|
||||
<p>
|
||||
{% trans %}
|
||||
To include an add-on in this collection, enter its name
|
||||
in the box below and hit enter. You can also use the
|
||||
<strong>Add to Collection</strong> links throughout the
|
||||
site to include add-ons later.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Add-on</th>
|
||||
<th>Comment</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input id="addon-ac" data-src="{{ url('search.ajax') }}" />
|
||||
<button id="addon-select">
|
||||
{{ _('Add to Collection') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% for addon in addons %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="hidden" value="{{ addon.id }}" name="addon">
|
||||
<img src="{{ addon.icon_url }}">
|
||||
<p>{{ addon.name }}</p>
|
||||
<p>
|
||||
<textarea name="addon_comment">{{ comments.get(addon.id) }}
|
||||
</textarea>
|
||||
</p>
|
||||
</td>
|
||||
<td class="comment">x</td>
|
||||
<td class="remove">x</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</fieldset>
|
||||
<p>
|
||||
<input type="submit" value="{{ _('Create Collection') }}">
|
||||
</p>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "bandwagon/edit_base.html" %}
|
||||
|
||||
|
||||
{% block form %}
|
||||
<form method="post" action="{{ url('collections.edit', username, slug) }}"
|
||||
enctype="multipart/form-data">
|
||||
{% include 'bandwagon/includes/addedit.html' %}
|
||||
<p>
|
||||
<input type="submit" value="{{ _('Save Changes') }}">
|
||||
</p>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "bandwagon/edit_base.html" %}
|
||||
{% block form %}
|
||||
<form method="post"
|
||||
action="{{ url('collections.edit_addons', username, slug) }}">
|
||||
{% include 'bandwagon/includes/addon_selector.html' %}
|
||||
{{ csrf() }}
|
||||
<p>
|
||||
<input type="submit" value="{{ _('Save Changes') }}">
|
||||
</p>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,52 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{# L10n: %s is the name of the collection. #}
|
||||
{{ page_title(_('Edit %s') % collection.name) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
{{ breadcrumbs([(remora_url('collections'), _('Collections')),
|
||||
(collection.get_url_path(), collection.name),
|
||||
(None, _('Edit'))]) }}
|
||||
|
||||
<h2>{{ _('Editing %s') % collection.name }}</h2>
|
||||
</header>
|
||||
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
||||
{{- message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if collection.owned_by(amo_user) %}
|
||||
<ul class="tabs">
|
||||
<li>
|
||||
<a href="{{ url('collections.edit', username, slug) }}">
|
||||
{{ _('Description') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('collections.edit_addons', username, slug) }}">
|
||||
{{ _('Add-ons') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('collections.edit_contributors', username, slug) }}">
|
||||
{{ _('Contributors & More') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if form %}
|
||||
{% include 'bandwagon/includes/addedit_errors.html' %}
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{% block form %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{% macro user_row(user, role) %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ user.name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ user.email }}
|
||||
</td>
|
||||
<td>
|
||||
{{ role }}
|
||||
|
||||
{% if role != _('Owner') %}
|
||||
<input type="hidden" name="contributor" value="{{ user.id }}">
|
||||
<a>Make Owner</a>
|
||||
<button name="new_owner" value="{{ user.id }}">POTCH IT!</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if role != _('Owner') %}
|
||||
<td class="remove">x</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
{% extends "bandwagon/edit_base.html" %}
|
||||
{% block bodyclass %}collections-contributors{% endblock %}
|
||||
{% block form %}
|
||||
<h3>{{ _('Collection Contributors') }}</h3>
|
||||
<p>
|
||||
{% trans %}
|
||||
You can add multiple contributors to this collection. A contributor can
|
||||
add and remove add-on from this collection, but cannot change its name or
|
||||
description. To add a contributor, enter their email in the box below.
|
||||
Contributors must have a Mozilla Add-ons account.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<form method="post"
|
||||
action="{{ url('collections.edit_contributors', username, slug) }}">
|
||||
|
||||
{{ csrf() }}
|
||||
<fieldset>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('User') }}</th>
|
||||
<th>{{ _('Email') }}</th>
|
||||
<th>{{ _('Role') }}</th>
|
||||
<th>{{ _('Remove') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<input id="contributor-ac" data-src="{{ url('users.ajax') }}"
|
||||
data-self="{{ amo_user.id }}" />
|
||||
<button id="contributor-ac-button">
|
||||
{{ _('Add Contributor') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{ user_row(collection.author, _('Owner')) }}
|
||||
{% for user in collection.users.all() %}
|
||||
{{ user_row(user, _('Contributor')) }}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
{% if is_admin %}
|
||||
<h3>{{ _('Admin Settings') }}</h3>
|
||||
{{ admin_form.as_p()|safe }}
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<input type="submit" value="{{ _('Save Changes') }}">
|
||||
</p>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
|||
{{ csrf() }}
|
||||
|
||||
<h3>{{ _('Collection Description') }}</h3>
|
||||
<fieldset>
|
||||
<p>
|
||||
{{ form.errors['name']|safe }}
|
||||
{{ form.name.label|safe }}
|
||||
{{ form.name|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.errors['slug']|safe }}
|
||||
{{ form.slug.label|safe }}
|
||||
{{ url('collections.user', user.get_profile().nickname)|absolutify -}}
|
||||
{{ form.slug|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.description.label|safe }} {{ _('(optional)') }}
|
||||
{{ form.description|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.listed.label|safe }}
|
||||
</p>
|
||||
{{ form.listed|safe }}
|
||||
<p>
|
||||
{% if collection %}
|
||||
<img src="{{ collection.icon_url }}">
|
||||
{% endif %}
|
||||
|
||||
{{ form.errors['icon']|safe }}
|
||||
{{ form.icon.label|safe }} {{ _('(optional)') }}
|
||||
{{ form.icon|safe }}
|
||||
</p>
|
||||
<p>
|
||||
{{ _('PNG and JPG supported. Image will be resized to 32x32.') }}
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{% if form.errors %}
|
||||
<p class="error">
|
||||
{{ _('There are errors in this form. Please correct them below.') }}
|
||||
</p>
|
||||
{% endif %}
|
|
@ -0,0 +1,47 @@
|
|||
<h3>{{ _('Add-ons in Your Collection') }}</h3>
|
||||
<p>
|
||||
{% trans %}
|
||||
To include an add-on in this collection, enter its name
|
||||
in the box below and hit enter. You can also use the
|
||||
<strong>Add to Collection</strong> links throughout the
|
||||
site to include add-ons later.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('Add-on') }}</th>
|
||||
<th>{{ _('Comment') }}</th>
|
||||
<th>{{ _('Remove') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input id="addon-ac" data-src="{{ url('search.ajax') }}" />
|
||||
<button id="addon-select">
|
||||
{{ _('Add to Collection') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% for addon in addons %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="hidden" value="{{ addon.id }}" name="addon">
|
||||
<img src="{{ addon.icon_url }}">
|
||||
<p>{{ addon.name }}</p>
|
||||
<p>
|
||||
<textarea name="addon_comment">{{ comments.get(addon.id) }}
|
||||
</textarea>
|
||||
</p>
|
||||
</td>
|
||||
<td class="comment">x</td>
|
||||
<td class="remove">x</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</fieldset>
|
|
@ -6,7 +6,7 @@ import test_utils
|
|||
|
||||
import amo
|
||||
from addons.models import Addon, AddonRecommendation
|
||||
from bandwagon.models import (Collection, SyncedCollection,
|
||||
from bandwagon.models import (Collection, CollectionUser, SyncedCollection,
|
||||
RecommendedCollection)
|
||||
from users.models import UserProfile
|
||||
|
||||
|
@ -94,6 +94,14 @@ class TestCollections(test_utils.TestCase):
|
|||
eq_(get_addons(c), addons)
|
||||
eq_(c.addons.count(), len(addons))
|
||||
|
||||
def test_is_publisher(self):
|
||||
c = Collection()
|
||||
u = UserProfile(nickname='f')
|
||||
c.save()
|
||||
u.save()
|
||||
CollectionUser(collection=c, user=u).save()
|
||||
eq_(c.is_publisher(u), True)
|
||||
|
||||
|
||||
class TestRecommendations(test_utils.TestCase):
|
||||
fixtures = ['base/addon-recs']
|
||||
|
|
|
@ -3,9 +3,12 @@ import os
|
|||
from django.conf import settings
|
||||
from django.http import QueryDict
|
||||
|
||||
from mock import patch
|
||||
from nose.tools import eq_
|
||||
from pyquery import PyQuery as pq
|
||||
import test_utils
|
||||
|
||||
from access import acl
|
||||
from amo.urlresolvers import reverse
|
||||
from amo.utils import urlparams
|
||||
from bandwagon.models import Collection, CollectionVote
|
||||
|
@ -128,7 +131,7 @@ class TestVotes(test_utils.TestCase):
|
|||
eq_(r.status_code, 200)
|
||||
|
||||
|
||||
class TestAdd(test_utils.TestCase):
|
||||
class TestCRUD(test_utils.TestCase):
|
||||
"""Test the collection form."""
|
||||
fixtures = ['base/fixtures']
|
||||
|
||||
|
@ -144,6 +147,12 @@ class TestAdd(test_utils.TestCase):
|
|||
'listed': 'True'
|
||||
}
|
||||
|
||||
def login_regular(self):
|
||||
self.client.login(username='regular@mozilla.com', password='password')
|
||||
|
||||
def create_collection(self):
|
||||
return self.client.post(self.add_url, self.data, follow=True)
|
||||
|
||||
def test_showform(self):
|
||||
"""Shows form if logged in."""
|
||||
r = self.client.get(self.add_url)
|
||||
|
@ -166,3 +175,91 @@ class TestAdd(test_utils.TestCase):
|
|||
r = self.client.post(self.add_url, self.data, follow=True)
|
||||
eq_(r.context['form'].errors['slug'][0],
|
||||
'This url is already in use by another collection')
|
||||
|
||||
def test_reassign(self):
|
||||
"""
|
||||
When reassigning an addon make sure we don't give it a duplicate slug.
|
||||
"""
|
||||
|
||||
# Create an addon by user 1.
|
||||
r = self.client.post(self.add_url, self.data, follow=True)
|
||||
|
||||
# Create an addon by user 2 with matching slug.
|
||||
self.login_regular()
|
||||
r = self.client.post(self.add_url, self.data, follow=True)
|
||||
# Add user1 to user 2.
|
||||
|
||||
# Make user1 owner of user2s addon.
|
||||
url = reverse('collections.edit_contributors',
|
||||
args=['regularuser', 'pornstar'])
|
||||
r = self.client.post(url,
|
||||
{'contributor': 4043307, 'new_owner': 4043307},
|
||||
follow=True)
|
||||
# verify that user1's addon is slug + '-'
|
||||
c = Collection.objects.get(slug='pornstar-')
|
||||
eq_(c.author_id, 4043307)
|
||||
|
||||
def test_edit(self):
|
||||
self.create_collection()
|
||||
url = reverse('collections.edit', args=['admin', 'pornstar'])
|
||||
r = self.client.get(url, follow=True)
|
||||
eq_(r.status_code, 200)
|
||||
|
||||
def test_edit_post(self):
|
||||
"""
|
||||
Test edit of collection.
|
||||
"""
|
||||
r = self.client.post(self.add_url, self.data, follow=True)
|
||||
url = reverse('collections.edit',
|
||||
args=['admin', 'pornstar'])
|
||||
|
||||
r = self.client.post(url,
|
||||
{'name': 'HALP', 'slug': 'halp', 'listed': True},
|
||||
follow=True)
|
||||
c = Collection.objects.get(slug='halp')
|
||||
eq_(unicode(c.name), 'HALP')
|
||||
|
||||
def test_forbidden_edit(self):
|
||||
r = self.client.post(self.add_url, self.data, follow=True)
|
||||
self.login_regular()
|
||||
url_args = ['admin', 'pornstar']
|
||||
|
||||
|
||||
url = reverse('collections.edit', args=url_args)
|
||||
r = self.client.get(url)
|
||||
eq_(r.status_code, 403)
|
||||
|
||||
url = reverse('collections.edit_addons', args=url_args)
|
||||
r = self.client.get(url)
|
||||
eq_(r.status_code, 403)
|
||||
|
||||
url = reverse('collections.edit_contributors', args=url_args)
|
||||
r = self.client.get(url)
|
||||
eq_(r.status_code, 403)
|
||||
|
||||
def test_edit_addons(self):
|
||||
self.create_collection()
|
||||
url = reverse('collections.edit_addons', args=['admin', 'pornstar'])
|
||||
r = self.client.get(url, follow=True)
|
||||
eq_(r.status_code, 200)
|
||||
|
||||
def test_edit_addons_post(self):
|
||||
self.create_collection()
|
||||
url = reverse('collections.edit_addons',
|
||||
args=['admin', 'pornstar'])
|
||||
r = self.client.post(url, {'addon': 40}, follow=True)
|
||||
addon = Collection.objects.filter(slug='pornstar')[0].addons.all()[0]
|
||||
eq_(addon.id, 40)
|
||||
|
||||
@patch('access.acl.action_allowed')
|
||||
def test_admin(self, f):
|
||||
f = lambda *args, **kwargs: True
|
||||
self.create_collection()
|
||||
url = reverse('collections.edit_contributors',
|
||||
args=['admin', 'pornstar'])
|
||||
r = self.client.get(url, follow=True)
|
||||
doc = pq(r.content)
|
||||
eq_(doc('form h3').text(), 'Admin Settings')
|
||||
|
||||
r = self.client.post(url, dict(application=1, type=0), follow=True)
|
||||
eq_(r.status_code, 200)
|
||||
|
|
|
@ -2,11 +2,18 @@ from django.conf.urls.defaults import patterns, url, include
|
|||
|
||||
from . import views
|
||||
|
||||
edit_urls = patterns('',
|
||||
url('^$', views.edit, name='collections.edit'),
|
||||
url('^addons$', views.edit_addons, name='collections.edit_addons'),
|
||||
url('^contributors$', views.edit_contributors,
|
||||
name='collections.edit_contributors'),
|
||||
)
|
||||
|
||||
detail_urls = patterns('',
|
||||
url('^$', views.collection_detail, name='collections.detail'),
|
||||
url('^vote/(?P<direction>up|down)$', views.collection_vote,
|
||||
name='collections.vote'),
|
||||
url('^edit/', include(edit_urls)),
|
||||
)
|
||||
|
||||
ajax_urls = patterns('',
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import functools
|
||||
|
||||
from django import http
|
||||
from django.db.models import Q
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
||||
import jingo
|
||||
from tower import ugettext_lazy as _lazy
|
||||
from tower import ugettext_lazy as _lazy, ugettext as _
|
||||
|
||||
import amo.utils
|
||||
from amo.decorators import login_required
|
||||
from amo.urlresolvers import reverse
|
||||
from access import acl
|
||||
from amo.decorators import login_required
|
||||
from amo.urlresolvers import reverse
|
||||
from addons.models import Addon
|
||||
from addons.views import BaseFilter
|
||||
from tags.models import Tag
|
||||
|
@ -17,6 +22,30 @@ from .models import Collection, CollectionAddon, CollectionUser, CollectionVote
|
|||
from . import forms
|
||||
|
||||
|
||||
def owner_required(f=None, require_owner=True):
|
||||
"""Requires collection to be owner, by someone."""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(request, username, slug, *args, **kw):
|
||||
collection = get_object_or_404(Collection,
|
||||
author__nickname=username,
|
||||
slug=slug)
|
||||
|
||||
if acl.check_collection_ownership(request, collection,
|
||||
require_owner=require_owner):
|
||||
return func(request, collection, username, slug, *args, **kw)
|
||||
else:
|
||||
return http.HttpResponseForbidden(
|
||||
_("This is not the collection you are looking for."))
|
||||
return wrapper
|
||||
|
||||
if f:
|
||||
return decorator(f)
|
||||
else:
|
||||
return decorator
|
||||
|
||||
|
||||
def legacy_redirect(request, uuid):
|
||||
# Nicknames have a limit of 30, so len == 36 implies a uuid.
|
||||
key = 'uuid' if len(uuid) == 36 else 'nickname'
|
||||
|
@ -159,6 +188,10 @@ def collection_vote(request, username, slug, direction):
|
|||
return redirect(c.get_url_path())
|
||||
|
||||
|
||||
def initial_data_from_request(request):
|
||||
return dict(author=request.amo_user, application_id=request.APP.id)
|
||||
|
||||
|
||||
@login_required
|
||||
def add(request):
|
||||
"Displays/processes a form to create a collection."
|
||||
|
@ -166,14 +199,17 @@ def add(request):
|
|||
if request.method == 'POST':
|
||||
form = forms.CollectionForm(
|
||||
request.POST, request.FILES,
|
||||
initial={'author': request.amo_user,
|
||||
'application_id': request.APP.id})
|
||||
initial=initial_data_from_request(request))
|
||||
aform = forms.AddonsForm(request.POST)
|
||||
if form.is_valid():
|
||||
collection = form.save()
|
||||
|
||||
if aform.is_valid():
|
||||
aform.save(collection)
|
||||
return http.HttpResponseRedirect(collection.get_url_path())
|
||||
else:
|
||||
data['addons'] = form.clean_addon()
|
||||
data['comments'] = form.clean_addon_comment()
|
||||
data['addons'] = aform.clean_addon()
|
||||
data['comments'] = aform.clean_addon_comment()
|
||||
else:
|
||||
form = forms.CollectionForm()
|
||||
|
||||
|
@ -223,7 +259,7 @@ def _ajax_add_remove(request, op):
|
|||
|
||||
c = Collection.objects.get(pk=id)
|
||||
|
||||
if not c.is_owner(request.amo_user):
|
||||
if not c.owned_by(request.amo_user):
|
||||
return http.HttpResponseForbidden()
|
||||
|
||||
a = Addon.objects.get(pk=addon_id)
|
||||
|
@ -244,3 +280,69 @@ def ajax_add(request):
|
|||
|
||||
def ajax_remove(request):
|
||||
return _ajax_add_remove(request, 'remove')
|
||||
|
||||
|
||||
@login_required
|
||||
@owner_required
|
||||
def edit(request, collection, username, slug):
|
||||
if request.method == 'POST':
|
||||
form = forms.CollectionForm(request.POST, request.FILES,
|
||||
initial=initial_data_from_request(request),
|
||||
instance=collection)
|
||||
if form.is_valid():
|
||||
collection = form.save()
|
||||
|
||||
return http.HttpResponseRedirect(collection.get_url_path())
|
||||
else:
|
||||
form = forms.CollectionForm(instance=collection)
|
||||
|
||||
data = dict(collection=collection,
|
||||
form=form,
|
||||
username=username,
|
||||
slug=slug)
|
||||
return jingo.render(request, 'bandwagon/edit.html', data)
|
||||
|
||||
|
||||
@login_required
|
||||
@owner_required(require_owner=False)
|
||||
def edit_addons(request, collection, username, slug):
|
||||
if request.method == 'POST':
|
||||
form = forms.AddonsForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save(collection)
|
||||
return http.HttpResponseRedirect(collection.get_url_path())
|
||||
|
||||
data = dict(collection=collection, username=username, slug=slug)
|
||||
return jingo.render(request, 'bandwagon/edit_addons.html', data)
|
||||
|
||||
|
||||
@login_required
|
||||
@owner_required
|
||||
def edit_contributors(request, collection, username, slug):
|
||||
is_admin = acl.action_allowed(request, 'Admin', '%')
|
||||
|
||||
data = dict(collection=collection, username=username, slug=slug,
|
||||
is_admin=is_admin)
|
||||
|
||||
if is_admin:
|
||||
initial = dict(type=collection.type,
|
||||
application=collection.application_id)
|
||||
data['admin_form'] = forms.AdminForm(initial=initial)
|
||||
|
||||
if request.method == 'POST':
|
||||
if is_admin:
|
||||
admin_form = forms.AdminForm(request.POST)
|
||||
if admin_form.is_valid():
|
||||
admin_form.save(collection)
|
||||
|
||||
form = forms.ContributorsForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save(collection)
|
||||
messages.success(request, _('Your collection has been updated.'))
|
||||
if form.cleaned_data['new_owner']:
|
||||
return http.HttpResponseRedirect(collection.get_url_path())
|
||||
return http.HttpResponseRedirect(
|
||||
reverse('collections.edit_contributors',
|
||||
args=[username, slug]))
|
||||
|
||||
return jingo.render(request, 'bandwagon/edit_contributors.html', data)
|
||||
|
|
|
@ -2456,3 +2456,11 @@ h6.author, .author a {
|
|||
background-image: url(../../img/zamboni/loading-white.gif);
|
||||
background-position: left bottom;
|
||||
}
|
||||
|
||||
.collections-contributors tr a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collections-contributors tr:hover a {
|
||||
display: inline;
|
||||
}
|
||||
|
|
|
@ -441,7 +441,43 @@ table.delegate(".remove", "click", function() {
|
|||
row.find('textarea').parent().show();
|
||||
});
|
||||
|
||||
|
||||
})();
|
||||
|
||||
if ($('body.collections-contributors')) {
|
||||
|
||||
var user_row = _.template('<tr>' +
|
||||
'<td>' +
|
||||
'<input name="contributor" value="{{ id }}" type="hidden">' +
|
||||
'{{ name }}' +
|
||||
'</td><td>{{ email }}</td>' +
|
||||
'<td class="contributor">Contributor</td>' +
|
||||
'<td class="remove">x</td>' +
|
||||
'</tr>'
|
||||
);
|
||||
|
||||
$('#contributor-ac-button').click(function(e) {
|
||||
e.preventDefault();
|
||||
var email = $('#contributor-ac').val();
|
||||
var src = $('#contributor-ac').attr('data-src');
|
||||
var my_id = $('#contributor-ac').attr('data-self');
|
||||
|
||||
// TODO(potch): Add a fancy failure case.
|
||||
$.get(src, {q: email}, function(d) {
|
||||
|
||||
// TODO(potch): gently yell at user if they add someone twice.
|
||||
if ($('input[name=contributor][value='+d.id+']').length == 0 &&
|
||||
my_id != d.id) {
|
||||
var str = user_row({id: d.id, name: d.name, email: email});
|
||||
$('#contributor-ac-button').closest('tbody').append(str);
|
||||
}
|
||||
|
||||
$('#contributor-ac').val('');
|
||||
});
|
||||
});
|
||||
|
||||
var table = $('#contributor-ac').closest('table');
|
||||
table.delegate(".remove", "click", function() {
|
||||
$(this).closest('tr').remove();
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче