ndarville / pony-forum

A modern alternative to ancient forum CMSes like vBulletin and PHPBB in Python on Django. (Alpha stage.) (NB: dotCloud have since removed their free Sandbox tier.)
http://pony-forum.com
26 stars 7 forks source link

includes/manage-users.html #107

Open ndarville opened 11 years ago

ndarville commented 11 years ago

Use the admin back-end for this for the time being? Problems:

Probably better to focus on the moderation/administration-specific views for the time being. Then the co-editor thingy can be done last.

Afterwards, create a basic moderator group and check the documentation.


  1. Sketch
  2. Use x as template, as it has the most widgets
  3. Disabled sections that won’t be used
  4. Rewrite admins.py
  5. Write default group postinstall and read
  6. Permissions
  7. Template

Consider per-object permissions later (thread, category).

Default Permissions


Manage Co-Editor
================

Template
--------
1. entries.html
    * edit post
    * manage co-editors
2. manage co-editor

Views/Permissions
-----------------
1. edit()
2. co_editors()

Problems
--------
1. No system for picking users from a list
2. Preferable to detect whether a post is OP for DB optimization
ndarville commented 11 years ago

IMG_0836 IMG_0838

ndarville commented 11 years ago

I probably need to only allow a user to be in one group.

ndarville commented 11 years ago

Can’t figure out what the field name for the User.groups.through objects is for defining its presentation with filter_horizontal.

It is possible that it won’t even work, but that seems unlikely.

ndarville commented 11 years ago

Using the admin is going to be a mess, because it still won’t scale with a large number of objects.

Linked issue trackers:

A default search system needs to be written, I wager.


  1. http://books.google.dk/books?id=Gpr7J7-FFmwC&pg=PA114&lpg=PA114&dq=django+%22filter_horizontal%22&source=bl&ots=_uWHemKDRD&sig=iqX6DdVD_tMbJcSdrBP57RhmtgA&hl=en&sa=X&ei=JShPUcqIGOTK4ASTyoD4Bg&redir_esc=y#v=onepage&q=django%20%22filter_horizontal%22&f=false
  2. http://stackoverflow.com/search?q=%5Bdjango%5D%20%22filter_horizontal%22
  3. http://fragmentsofcode.wordpress.com/2009/05/26/tweaking-django-auth-admin/

Reproducing the look and behaviour:

  1. http://chase-seibert.github.com/blog/2010/05/14/reuse-djangos-filter_horizontal-admin-widget.html
  2. http://www.hoboes.com/Mimsy/hacks/replicating-djangos-admin/reusing-djangos-filter_horizontal/
ndarville commented 11 years ago

For all:


For co-editors:

ndarville commented 11 years ago

simple_js() and nonjs() rely on an action and an object_id:

verb object

manage_users() would rely on an action, an object_id, and another_object_id:

verb object in another_object

Question is whether to use—or extend—simple_js() and nonjs() or to go a new route.

ndarville commented 11 years ago

Resource: http://effectivedjango.com/forms.html

The documentation makes it sound as if the queries are sanitized.

ndarville commented 11 years ago

A URL like /manage/co-editors/5 or /manage/co-editors/5/3/ doesn’t even make a lot of sense, since the digit represents the thread and not the object ID of one or several co-editors.

Maybe something like /manage/thread/5/co-editors/3/? It doesn’t look very standardized, though.

I’ll try to find some examples.

ndarville commented 11 years ago

Excised for now:

urls.py

  (r'^manage/(?P<user_type>\w+(?:-\w+)?)/(?P<object_id>\d+)/$',
                                               'manage_users'),
  (r'^manage/js/$',                            'manage_users_js'),

views.py

@login_required()
def manage_users(request, user_type, object_id):
    """Inspect and edit permissions and groups for

    1. Post co-editors
    2. Moderators
    3. Groups

    The POST submissions to promote and demote co-editors are handled by views
    simple_js() and nonjs().
    """
    people, thread, query = None, None, None

  # if not user_type in ['co-editors', 'moderators', 'groups']:
    if not user_type == 'co-editors':
        return "404"

    if user_type == 'co-editors':
        thread = get_object_or_404(Thread, pk=object_id)

        if thread.is_removed:
            messages.info(request, "This thread no longer exists.")
            return HttpResponseRedirect(reverse('forum.views.thread',
                args=(thread.id,)))
        elif (not request.user == thread.author and
              not request.user.has_perm('forum.appoint_coeditor')):
            messages.info(request,
                "You are not authorized to assign co-editors to this thread.")
            return HttpResponseRedirect(reverse('forum.views.thread',
                args=(thread.id,)))

    if request.method == "POST":
        if request.POST['user-id-search'] != "":  # Search by user ID
            try:
                people = [User.objects.get(pk=request.POST['user-id-search'])]
            except ObjectDoesNotExist:
                messages.info(request, "No user matching query exists.")
        elif 'username-search' in request.POST:  # Search by username
            #TODO Hide users already in other list
            query = request.POST['username-search']
            people = search(request, query)
    # elif user_type in ['moderators', 'groups'] and not request.user.is_superusers:
    #     messages.info("You do not have permission to access this page.")
    #     return HttpResponseRedirect(reverse('forum.views.home', args=()))
        elif 'mote' in request.POST:  # (Pro/De)mote
            coeditor = get_object_or_404(User, pk=request.POST[''])
            if 'promote' in request.POST:
                thread.coeditors.add(coeditor)
            else:  # Demote
                thread.coeditors.remove(coeditor)

    return render(request, 'manage_users.html', {
        'user_type': user_type,
        'object_id': object_id,
        'people':    people,
        'thread':    thread,
        'query':     query})

def manage_users_js(request):
    """Lets users do one of the following:

    * add co-editors to a thread
    * add users to a group of moderators
    * create new groups, and modify group permissions
    """
    if request.is_ajax() and request.method == "POST":
        action = request.POST['action']
        thread = Thread.objects.get(pk=request.POST['object_id'])
        person = User.objects.get(pk=request.POST['user_id'])

        if action == "Promote" or action == "Demoted":
            thread.coeditors.add(person)
            new_action = "Promoted"
        else:
            thread.coeditors.remove(person)
            new_action = "Demoted"

        thread.save()

    return HttpResponse(new_action)

manage-users-js.js

$(document).ready(function() {
// This CSRF token allows us to make POST requests
    $(document).ajaxSend(function(event, xhr, settings) {
        function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie != '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = jQuery.trim(cookies[i]);
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) == (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }
        function sameOrigin(url) {
            // url could be relative or scheme relative or absolute
            var host = document.location.host, // host + port
                protocol = document.location.protocol,
                sr_origin = '//' + host,
                origin = protocol + sr_origin;
            // Allow absolute or scheme relative URLs to same origin
            return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
                (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
                // or any other URL that isn't scheme relative or absolute i.e relative.
                !(/^(\/\/|http:|https:).*/.test(url));
        }
        function safeMethod(method) {
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }

        if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
            xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
        }
    });

    $('.js').on('click', function(e) {
        // Only perform the following if user is logged in,
        // detected by checking for a "Log out" in navigation.
        if ($('.last:contains("Log Out")').length) {
            // Overrule default nonjs action when submit button is clicked
            // to allow handling the the logic with our JavaScript instead.
            e.preventDefault();

            var $this     = $(this),
                object_id = $("#thread-id").val(),
                user_id   = this.id,
                action    = $this.text()
                // object_id   = this.id,      // id="{{ person.id }}"
                // object_type = "thread",
                // user_type   = "co-editor",
                // href        = this.href;    // Will that suffice here?

            $.post("/manage/js/", {
                object_id: object_id,
                user_id:   user_id,
                action:    action
                // href:      href
                },
                function(data) {
                    $this.text(data);
            });
        }
    });
});

manage_users.html

{% extends "page.html" %}

{% block title %}Manage {{ user_type|capfirst }}{% endblock %}
{% block canonical_url %}{% url forum.views.manage_users user_type object_id %}{% endblock %}
{% block content_body %}
    <form action="{% url forum.views.manage_users user_type object_id %}" method="post" id="search-form">
        {% csrf_token %}
        <label for="search" role="search">Search</label>
        <input
            type="search"
            x-webkit-speech
            name="username-search"
            value=""
            placeholder="Search for user by name"
        />
        <input
            type="number"
            x-webkit-speech
            name="user-id-search"
            value=""
            min="1"
            step="1"
            placeholder="Search by ID"
        />

        {% comment %}
            {% if people %}<p>Matches for usernames with &ldquo;{{ query }}&rdquo;</p>{% endif %}
        {% endcomment %}

        <div id="left-aligned-button-group">
        {% if user_type == "co-editors" %}
        {% with editors=thread.coeditors.all %}
            {% if people %}
                <ul class="user-list">
                    {% for person in people %}
                        <li><a class="button js" role="button" href="" id="{{ person.id }}">Promote</a> {{ person.username }}</li>
                    {% endfor %}
                </ul>
                <hr />
            {% endif %}
        <!-- Current co-editors -->
            {% if editors %}
                <ul class="user-list">
                    {% for person in editors %}
                        <li><a class="button js" role="button" href="" id="{{ person.id }}">Demote</a> {{ person.username }}</li>
                    {% endfor %}
                </ul>
            {% else %}
                <p>This thread currently has no co-editors.</p>
            {% endif %}
        {% endwith %}
        {% endif %}
        </div>

        <div id="button-group">
            <input type="submit" value="Search" />
            <input type="hidden" id="thread-id" value="{{ object_id }}" />
        </div>
    </form>
{% endblock %}
{% block js %}
        {% include "includes/jquery.html" %}
            <script type="text/javascript" src="{{ STATIC_URL }}js/manage-users-js.js"></script>
{% endblock %}
ndarville commented 11 years ago
elif (not request.user == thread.author and
          not request.user.has_perm('forum.appoint_coeditor')):
ndarville commented 10 years ago

Check CSS diff and implement in /sass branch.

ndarville commented 10 years ago

Co-editor changes got merged: e8bed6ee23193e4b2a03556a0d27f1579b1a98d5. Punted on other management for now.