Changeset - cc3224bb60dd
www/conservancy/settings.py
Show inline comments
...
 
@@ -100,7 +100,7 @@ INSTALLED_APPS = [
 
    'conservancy.apps.assignment',
 
    'conservancy.apps.fossy',
 
    'podjango',
 
    'usethesource',
 
    'usethesource.apps.UseTheSourceConfig',
 
]
 

	
 
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
www/usethesource/admin.py
Show inline comments
 
new file 100644
 
from django.contrib import admin
 

	
 
from .models import Candidate, Comment
 

	
 

	
 
class CommentInline(admin.TabularInline):
 
    model = Comment
 
    fields = ['user', 'message']
 
    extra = 0
 

	
 

	
 
@admin.register(Candidate)
 
class CandidateAdmin(admin.ModelAdmin):
 
    list_display = ['name', 'vendor', 'device', 'release_date']
 
    fields = [
 
        'name',
 
        'slug',
 
        'vendor',
 
        'device',
 
        'release_date',
 
        'source_url',
 
        'binary_url',
 
        'description',
 
    ]
 
    inlines = [CommentInline]
 
    prepopulated_fields = {'slug': ['name']}
www/usethesource/apps.py
Show inline comments
 
new file 100644
 
from django.apps import AppConfig
 

	
 

	
 
class UseTheSourceConfig(AppConfig):
 
    default_auto_field = 'django.db.models.AutoField'
 
    name = 'usethesource'
www/usethesource/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# Generated by Django 3.2.19 on 2023-10-24 08:53
 

	
 
from django.conf import settings
 
from django.db import migrations, models
 
import django.db.models.deletion
 

	
 

	
 
class Migration(migrations.Migration):
 

	
 
    initial = True
 

	
 
    dependencies = [
 
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 
    ]
 

	
 
    operations = [
 
        migrations.CreateModel(
 
            name='Candidate',
 
            fields=[
 
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 
                ('name', models.CharField(max_length=50, verbose_name='Candidate name')),
 
                ('slug', models.SlugField(unique=True)),
 
                ('vendor', models.CharField(max_length=50, verbose_name='Vendor name')),
 
                ('device', models.CharField(max_length=50, verbose_name='Device name')),
 
                ('release_date', models.DateField()),
 
                ('description', models.TextField()),
 
                ('source_url', models.URLField()),
 
                ('binary_url', models.URLField(blank=True)),
 
            ],
 
        ),
 
        migrations.CreateModel(
 
            name='Comment',
 
            fields=[
 
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 
                ('time', models.DateTimeField(auto_now_add=True)),
 
                ('message', models.TextField()),
 
                ('candidate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='usethesource.candidate')),
 
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
 
            ],
 
            options={
 
                'ordering': ['id'],
 
            },
 
        ),
 
    ]
www/usethesource/migrations/__init__.py
Show inline comments
 
new file 100644
www/usethesource/models.py
Show inline comments
 
new file 100644
 
from django.contrib.auth.models import User
 
from django.db import models
 

	
 

	
 
class Candidate(models.Model):
 
    name = models.CharField('Candidate name', max_length=50)
 
    slug = models.SlugField(max_length=50, unique=True)
 
    vendor = models.CharField('Vendor name', max_length=50)
 
    device = models.CharField('Device name', max_length=50)
 
    release_date = models.DateField()
 
    description = models.TextField()
 
    source_url = models.URLField()
 
    binary_url = models.URLField(blank=True)
 

	
 
    def __str__(self):
 
        return self.name
 

	
 

	
 
class Comment(models.Model):
 
    candidate = models.ForeignKey(Candidate, on_delete=models.CASCADE)
 
    user = models.ForeignKey(User, on_delete=models.PROTECT)
 
    time = models.DateTimeField(auto_now_add=True)
 
    message = models.TextField()
 

	
 
    def __str__(self):
 
        return f'{self.candidate.name}, {self.user}, {self.time}'
 

	
 
    class Meta:
 
        ordering = ['id']
www/usethesource/templates/usethesource/add_comment_button_partial.html
Show inline comments
 
new file 100644
 
<div class="mt2" hx-target="this" hx-swap="outerHTML">
 
  <button type="submit" class="f3 b white bg-green ph2" style="border: none" hx-get="{% url 'usethesource:add_comment' slug=candidate.slug %}">+</button>
 
</div>
www/usethesource/templates/usethesource/cancel_button_partial.html
Show inline comments
 
new file 100644
 
<button type="submit" class="b white bg-light-silver pv2 ph3" style="border: none">Cancel</button>
www/usethesource/templates/usethesource/candidate.html
Show inline comments
 
{% extends "usethesource/base.html" %}
 

	
 
{% block head %}
 
  {{ block.super }}
 
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
 
{% endblock %}
 

	
 
{% block content %}
 
  {{ block.super }}
 

	
 
  <section class="pa2 mt4 mb3">
 
    <div style="display: flex; justify-content: space-between">
 
      <div>
 
        <h2 class="f2 lh-title ttu mt0">Linksys WRG987-v2</h2>
 
        <p><strong>Vendor</strong>: Linksys</em></p>
 
        <p><strong>Device</strong>: WRG987</em></p>
 
        <p><strong>Released</strong>: Oct 20, 2023</em></p>
 
        <h2 class="f2 lh-title ttu mt0">{{ candidate.name }}</h2>
 
        <p><strong>Vendor</strong>: {{ candidate.vendor }}</em></p>
 
        <p><strong>Device</strong>: {{ candidate.device }}</em></p>
 
        <p><strong>Released</strong>: {{ candidate.release_date }}</em></p>
 
      </div>
 
      <div class="mt2">
 
        <div><a href="{% url 'usethesource:download' %}" class="white bg-green db pv2 ph3 mb2">Download source</a></div>
 
        <div><a href="{% url 'usethesource:download' %}" class="white bg-green db pv2 ph3">Download image</a></div>
 
        <div><a href="{% url 'usethesource:download' slug=candidate.slug download_type='source' %}" class="white bg-green db pv2 ph3 mb2">Download source</a></div>
 
        <div><a href="{% url 'usethesource:download' slug=candidate.slug download_type='binary' %}" class="white bg-green db pv2 ph3">Download image</a></div>
 
      </div>
 
    </div>
 

	
 
    <p>The WRG1830 Wireless Router Gateway (WRG) is a component of the ZFR183x Pro Series Wireless Field Bus System. The WRG1830 provides BACnet IP connectivity to compatible Johnson Controls Field Controllers, VAV Controllers, Thermostats, and Sensors over a wireless mesh network.</p>
 
    <p>{{ candidate.description }}</p>
 

	
 
    <h3 class="f3 lh-title mt4">Comments</h3>
 
    <p><strong>Denver — Oct 20, 2023</strong>:<br>
 
      I was able to run the autogen and configure scripts, but the build failed with a missing dependency XYZ.</p>
 
    {% for comment in candidate.comment_set.all %}
 
      {% include "usethesource/comment_partial.html" %}
 
    {% endfor %}
 

	
 
    <form method="post">
 
      <div class="mt4">
 
        <textarea placeholder="Add a comment..." style="width: 50em; height: 15em;"></textarea>
 
      </div>
 
      <div class="mt2">
 
        <button type="submit" class="b white bg-green pv2 ph3" style="border: none">Save</button>
 
      </div>
 
    </form>
 
    {% if user.is_staff %}
 
      {% include "usethesource/add_comment_button_partial.html" %}
 
    {% endif %}
 
  </section>
 
{% endblock content %}
www/usethesource/templates/usethesource/comment_deleted.html
Show inline comments
 
new file 100644
 
{% if add and user.is_staff %}
 
  {% include 'usethesource/add_comment_button_partial.html' %}
 
{% endif %}
www/usethesource/templates/usethesource/comment_form.html
Show inline comments
 
new file 100644
 
<form hx-target="this" hx-swap="outerHTML" hx-post="{% url 'usethesource:add_comment' slug=candidate.slug %}" method="post">
 
  {% csrf_token %}
 
  {{ form.message }}
 
  <div class="mt2">
 
    <button type="submit" hx-get="{% url 'usethesource:add_button' slug=candidate.slug %}" class="b pointer white bg-light-silver pv2 ph3" style="border: none">Cancel</button>
 
    {% include 'usethesource/save_button_partial.html' %}
 
  </div>
 
</form>
www/usethesource/templates/usethesource/comment_partial.html
Show inline comments
 
new file 100644
 
<div hx-target="this" hx-swap="outerHTML"><strong>{{ comment.user }} — {{ comment.time }}</strong>
 
  {% if user.is_staff %}
 
    <a href="#" class="f7 white bg-light-silver ph2" hx-get="{% url 'usethesource:edit_comment' comment_id=comment.id %}">edit</a>
 
    <a href="#" class="f7 white bg-light-red ph2" hx-delete="{% url 'usethesource:delete_comment' comment_id=comment.id show_add='false' %}" hx-confirm="Are you sure you want to delete this comment?">delete</a>
 
  {% endif %}
 
  <br>{{ comment.message|linebreaks }}
 
</div>
www/usethesource/templates/usethesource/download.html
Show inline comments
...
 
@@ -5,13 +5,13 @@
 

	
 
  <section class="pa2 mt4 mb5">
 
    <h2 class="f2 lh-title ttu mt0">Download</h2>
 
    <p><strong>File</strong>: xyz-image.tar.gz</p>
 
    <p><strong>File</strong>: {{ download_url }}</p>
 

	
 
    <p>By downloading this, you agree to use it only for the purposes of GPL compliance checking, unless otherwise permitted by the license.</p>
 

	
 
    <div style="display: flex">
 
      <a href="{% url 'usethesource:candidate' %}" class="white bg-silver dib pv2 ph3 mb2 mr2">Back</a>
 
      <a href="{% url 'usethesource:download' %}" class="white bg-green dib pv2 ph3 mb2">Download</a>
 
      <a href="{% url 'usethesource:candidate' slug=candidate.slug %}" class="white bg-silver dib pv2 ph3 mb2 mr2">Back</a>
 
      <a href="{{ download_url }}" class="white bg-green dib pv2 ph3 mb2">Download</a>
 
    </div>
 
  </section>
 
{% endblock content %}
www/usethesource/templates/usethesource/edit_comment_form.html
Show inline comments
 
new file 100644
 
<form class="mb3" hx-target="this" hx-swap="outerHTML" hx-post="{% url 'usethesource:edit_comment' comment_id=comment.id %}" method="post">
 
  {% csrf_token %}
 
  {{ form.message }}
 
  <div class="mt2">
 
    <button type="submit" hx-get="{% url 'usethesource:view_comment' comment_id=comment.id show_add='false' %}" class="b pointer white bg-light-silver pv2 ph3" style="border: none">Cancel</button>
 
    {% include 'usethesource/save_button_partial.html' %}
 
  </div>
 
</form>
www/usethesource/templates/usethesource/landing_page.html
Show inline comments
...
 
@@ -9,16 +9,12 @@
 

	
 
  <section class="mb5">
 
    <h2 class="f2 lh-title ttu mt0">Candidates</h2>
 
    <div class="mb4">
 
      <h3 class="f4 lh-title mt0"><a href="{% url 'usethesource:candidate' %}">Linksys WRG987-v2</a></h3>
 
      <p class=""><em>Released Oct 20, 2023</em></p>
 
      <p class="">The WRG1830 Wireless Router Gateway (WRG) is a component of the ZFR183x Pro Series Wireless Field Bus System. The WRG1830 provides BACnet IP connectivity to compatible Johnson Controls Field Controllers, VAV Controllers, Thermostats, and Sensors over a wireless mesh network.</p>
 
    </div>
 

	
 
    <div>
 
      <h2 class="lh-title mt0"><a href="{% url 'usethesource:candidate' %}">Linksys WRG987-v2</a></h2>
 
      <p><em>Released Oct 20, 2023</em></p>
 
      <p>The WRG1830 Wireless Router Gateway (WRG) is a component of the ZFR183x Pro Series Wireless Field Bus System. The WRG1830 provides BACnet IP connectivity to compatible Johnson Controls Field Controllers, VAV Controllers, Thermostats, and Sensors over a wireless mesh network.</p>
 
    </div>
 
    {% for candidate in candidates %}
 
      <div class="mb4">
 
        <h3 class="f4 lh-title mt0"><a href="{% url 'usethesource:candidate' slug=candidate.slug %}">{{ candidate.name }}</a></h3>
 
        <p><em>Released {{ candidate.release_date }}</em></p>
 
        <p>{{ candidate.description }}</p>
 
      </div>
 
    {% endfor %}
 
</section>
 
{% endblock content %}
www/usethesource/templates/usethesource/returned_comment.html
Show inline comments
 
new file 100644
 
{% include 'usethesource/comment_partial.html' %}
 

	
 
{% if add and user.is_staff %}
 
  {% include 'usethesource/add_comment_button_partial.html' %}
 
{% endif %}
www/usethesource/templates/usethesource/save_button_partial.html
Show inline comments
 
new file 100644
 
<button type="submit" class="b white bg-green pv2 ph3" style="border: none">Save</button>
www/usethesource/urls.py
Show inline comments
...
 
@@ -5,6 +5,11 @@ from . import views
 
app_name = 'usethesource'
 
urlpatterns = [
 
    path('', views.landing_page, name='landing'),
 
    path('candidate/', views.candidate_page, name='candidate'),
 
    path('download/', views.download_page, name='download'),
 
    path('candidate/<slug:slug>/', views.candidate_page, name='candidate'),
 
    path('download/<slug:slug>/<download_type>/', views.download_page, name='download'),
 
    path('add-comment/<slug:slug>/', views.create_comment, name='add_comment'),
 
    path('edit-comment/<int:comment_id>/', views.edit_comment, name='edit_comment'),
 
    path('comment/<int:comment_id>/<show_add>/', views.view_comment, name='view_comment'),
 
    path('delete-comment/<int:comment_id>/<show_add>/', views.delete_comment, name='delete_comment'),
 
    path('add-button/<slug:slug>/', views.add_button, name='add_button'),
 
]
www/usethesource/views.py
Show inline comments
 
from django.shortcuts import render
 
from django import forms
 
from django.contrib.admin.views.decorators import staff_member_required
 
from django.shortcuts import get_object_or_404, redirect, render
 

	
 
from .models import Candidate, Comment
 

	
 

	
 
def landing_page(request):
 
    return render(request, 'usethesource/landing_page.html', {})
 
    candidates = Candidate.objects.all()
 
    return render(request, 'usethesource/landing_page.html', {'candidates': candidates})
 

	
 

	
 
def candidate_page(request, slug):
 
    candidate = get_object_or_404(Candidate, slug=slug)
 
    return render(request, 'usethesource/candidate.html', {'candidate': candidate, 'add': True})
 

	
 

	
 
def download_page(request, slug, download_type):
 
    candidate = get_object_or_404(Candidate, slug=slug)
 
    url = candidate.source_url if download_type == 'source' else candidate.binary_url
 
    return render(
 
        request,
 
        'usethesource/download.html',
 
        {'candidate': candidate, 'download_url': url},
 
    )
 

	
 

	
 
class CommentForm(forms.ModelForm):
 
    class Meta:
 
        model = Comment
 
        fields = ['message']
 

	
 

	
 
@staff_member_required
 
def create_comment(request, slug):
 
    candidate = get_object_or_404(Candidate, slug=slug)
 
    if request.method == 'GET':
 
        form = CommentForm()
 
    else:
 
        form = CommentForm(request.POST)
 
        if form.is_valid():
 
            instance = form.save(commit=False)
 
            instance.candidate = candidate
 
            instance.user = request.user
 
            instance.save()
 
            return redirect('usethesource:view_comment', comment_id=instance.id, show_add='true')
 
    return render(request, 'usethesource/comment_form.html', {'form': form, 'candidate': candidate})
 

	
 

	
 
@staff_member_required
 
def edit_comment(request, comment_id):
 
    comment = get_object_or_404(Comment, id=comment_id)
 
    if request.method == 'GET':
 
        form = CommentForm(instance=comment)
 
    else:
 
        form = CommentForm(request.POST, instance=comment)
 
        if form.is_valid():
 
            instance = form.save(commit=False)
 
            instance.save()
 
            return redirect('usethesource:view_comment', comment_id=instance.id, show_add='false')
 
    return render(request, 'usethesource/edit_comment_form.html', {'form': form, 'comment': comment})
 

	
 

	
 
@staff_member_required
 
def view_comment(request, comment_id, show_add):
 
    show_add = show_add == 'true'
 
    comment = get_object_or_404(Comment, id=comment_id)
 
    return render(request, 'usethesource/returned_comment.html', {'comment': comment, 'candidate': comment.candidate, 'add': show_add})
 

	
 

	
 
def candidate_page(request):
 
    return render(request, 'usethesource/candidate.html', {})
 
@staff_member_required
 
def delete_comment(request, comment_id, show_add):
 
    show_add = show_add == 'true'
 
    Comment.objects.filter(id=comment_id).delete()
 
    return render(request, 'usethesource/comment_deleted.html', {'comment': None, 'add': show_add})
 

	
 

	
 
def download_page(request):
 
    return render(request, 'usethesource/download.html', {})
 
@staff_member_required
 
def add_button(request, slug):
 
    candidate = get_object_or_404(Candidate, slug=slug)
 
    return render(request, 'usethesource/add_comment_button_partial.html', {'candidate': candidate})
0 comments (0 inline, 0 general)