[PYTHON] Teilen Sie Daten mit Django (3) in projektähnliche Einheiten.

Vorwort

Dies ist eine Fortsetzung von diesem Artikel. Eine "projektähnliche Einheit" ist ein "Projekt" in einem Task-Management-Tool usw. Da das Theater verwaltet wird, entspricht die "Performance" diesem.

---> GitHub Repository (Python 3.8.1, Django 3.0.3)

Artikelinhalt

Letztes Mal habe ich über die Dateneingabeschnittstelle nachgedacht, die zu "Leistung" gehört. Dieses Mal möchte ich über einen Mechanismus nachdenken, mit dem andere Benutzer als ich eingeladen und zu den Mitgliedern der "Performance" hinzugefügt werden können.

Spezielle Ziele

—— Ermöglicht dem Eigentümer einer „Aufführung“, eine „Einladung“ für ein bestimmtes Konto zu erstellen.

Vollständiges Bild

Diejenigen, die einladen

Eingeladen

Organisieren Sie, was Sie tun möchten

In dieser Web-App ["Performance User"](https://qiita.com/satamame/items/959e21ade18c48e1b4d6#%E5%85%AC%E6%BC%94%E3%83%A6%E3%83%BC% E3% 82% B6% E3% 81% AE% E3% 83% A2% E3% 83% 87% E3% 83% AB) auf die von Django verwalteten Konten und jede "Leistung" Der Teilnahmestatus ist zugeordnet. Wenn die eingeladene Person "Beitreten" auswählt, wird daher ein neuer "Leistungsbenutzer" erstellt.

Machen Sie ein Modell der "Einladung"

Es hat die folgenden Felder.

Feld Inhalt
production Eingeladene "Leistung"
inviter Einladende Person(Von Django verwaltete Konten)
invitee Eingeladene Person(Von Django verwaltete Konten)
exp_dt Ablaufdatum der Einladung

Erstellen Sie einen Mechanismus zum Erstellen einer "Einladung"

Erstellen Sie einen Mechanismus zum Anzeigen von "Einladung"

--Wenn der Eigentümer von "Leistung" den Bildschirm "Mitgliederliste" anzeigt, wird die von ihm erstellte "Einladungskarte" angezeigt.

Erstellen Sie einen Mechanismus zur Teilnahme an "Performances" aus "Einladungen"

Einladungsmodell

from datetime import datetime, timezone
from django.conf import settings
from django.db import models


class Invitation(models.Model):
    '''Einladung zur Truppe
    '''
    production = models.ForeignKey(Production, verbose_name='Performance',
        on_delete=models.CASCADE)
    inviter = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='Einladende Person',
        related_name='inviter', on_delete=models.CASCADE)
    invitee = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='Eingeladene Person',
        related_name='invitee', on_delete=models.CASCADE)
    exp_dt = models.DateTimeField(verbose_name='Frist')
    
    class Meta:
        verbose_name = verbose_name_plural = 'Einladung zur Truppe'
        ordering = ['exp_dt']
    
    def __str__(self):
        return f'{self.invitee}Zu{self.production}Einladung an'
    
    def expired(self):
        '''Gibt zurück, ob diese Einladung abgelaufen ist
        '''
        now = datetime.now(timezone.utc)
        return now > self.exp_dt

--Production ist in dieser Web-App definiert (https://qiita.com/satamame/items/959e21ade18c48e1b4d6#%E5%85%AC%E6%BC%94-%E3%83%97%E3% 83% AD% E3% 82% B8% E3% 82% A7% E3% 82% AF% E3% 83% 88% E7% 9A% 84% E5% 8D% 98% E4% BD% 8D-% E3% 81 % AE% E3% 83% A2% E3% 83% 87% E3% 83% AB) Dies ist ein Modell für "Leistung".

Mechanismus zum Erstellen von Einladungen

Das Folgende ist eine Beschreibung von Ansicht und Vorlage. Da es in einer Anwendung namens "Produktion" erstellt wird, wird die URL als "Produktion: usr_list" angegeben. Informationen zum Schreiben von "urls.py" für den Aufruf finden Sie unter Quelle für "Produktion / URLs.py". Bitte.

Erstellen Sie eine Ansicht

Erstellen Sie eine Ansicht, die "CreateView" erbt, um eine Einladung zu erstellen.

views.py


from django.views.generic.edit import CreateView
from django.contrib import messages
from django.contrib.auth import get_user_model
from .view_func import *
from .models import Production, ProdUser, Invitation


class InvtCreate(LoginRequiredMixin, CreateView):
    '''Zusätzliche Ansicht der Einladung
    '''
    model = Invitation
    fields = ()

views.py


    def get(self, request, *args, **kwargs):
        '''Handler, um die Anforderung zum Zeitpunkt der Anzeige zu erhalten
        '''
        #Überprüfen Sie den Besitz, um Zugriff auf Leistungsbenutzer zu erhalten
        prod_user = test_owner_permission(self)
        
        #Produktion als Attribut der Sicht haben
        #Anzeige als festes Element in der Vorlage
        self.production = prod_user.production
        
        return super().get(request, *args, **kwargs)

views.py


    def post(self, request, *args, **kwargs):
        '''Handler, um die Anfrage beim Speichern zu erhalten
        '''
        #Überprüfen Sie den Besitz, um Zugriff auf Leistungsbenutzer zu erhalten
        prod_user = test_owner_permission(self)
        
        #"ID der eingeladenen Person" in das Formular eingegeben
        invitee_value = request.POST['invitee_id']
        
        #Haben Sie einen passenden Benutzer als Attribut der Ansicht
        user_model = get_user_model()
        invitees = user_model.objects.filter(username=invitee_value)
        if len(invitees) > 0:
            self.invitee = invitees[0]
        
        # prod_user,Produktion als Attribut der Sicht haben
        #Zur Verwendung während der Validierung und Lagerung
        self.prod_user = prod_user
        self.production = prod_user.production
        
        return super().post(request, *args, **kwargs)

――Auf dem Bildschirm zum Erstellen einer Einladung wird im Kontext der Benutzeroberfläche die "eingeladene Person" als "eingeladene Person" geschrieben.

views.py


    def form_valid(self, form):
        '''Nach bestandener Validierung
        '''
        #Hinzufügen fehlgeschlagen, wenn der POST-Handler den Benutzer nicht zum Einladen veranlasst hat
        if not hasattr(self, 'invitee'):
            return self.form_invalid(form)
        
        #Benutzer-ID-Liste der Leistungsbenutzer
        prod_users = ProdUser.objects.filter(production=self.production)
        prod_user_user_ids = [prod_user.user.id for prod_user in prod_users]
        
        #ID-Liste der eingeladenen Benutzer
        invitations = Invitation.objects.filter(production=self.production)
        current_invitee_ids = [invt.invitee.id for invt in invitations]
        
        #Es ist nicht möglich, Performance-Benutzer oder eingeladene Benutzer einzuladen.
        if self.invitee.id in prod_user_user_ids\
            or self.invitee.id in current_invitee_ids:
            return self.form_invalid(form)
        
        #Legen Sie jedes Feld des Datensatzes fest, den Sie hinzufügen möchten
        instance = form.save(commit=False)
        instance.production = self.production
        instance.inviter = self.prod_user.user
        instance.invitee = self.invitee
        #Einsendeschluss ist 7 Tage
        #Standardmäßig in UTC gespeichert, aber für alle Fälle UTC angeben
        instance.exp_dt = datetime.now(timezone.utc) + timedelta(days=7)
        
        messages.success(self.request, str(instance.invitee) + "Wurde eingeladen.")
        return super().form_valid(form)

views.py


    def get_success_url(self):
        '''Geben Sie das Übergangsziel dynamisch an, wenn das Hinzufügen erfolgreich ist
        '''
        prod_id = self.prod_user.production.id
        url = reverse_lazy('production:usr_list', kwargs={'prod_id': prod_id})
        return url
    
    def form_invalid(self, form):
        '''Wenn das Hinzufügen fehlschlägt
        '''
        messages.warning(self.request, "Ich konnte dich nicht einladen.")
        return super().form_invalid(form)

Erstellen Sie eine Vorlage

invitation_form.html


{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
<a href="{% url 'production:usr_list' prod_id=view.production.id %}">◀</a>
Einladung zur Truppe
</h1>

<div>&nbsp;</div>

<form method="post">
    {% csrf_token %}
    <table>
    <tr><th>Performance</th><td>{{ view.production }}</td></tr>
    <tr><th>ID der einladenden Person</th><td><input type="text" name="invitee_id" /></td></tr>
    </table>
    <input type="submit" value="Einladung">
</form>
{% endblock %}

Mechanismus zum Anzeigen der erstellten Einladung

Der Bildschirm, auf dem die von Ihnen erstellte Einladung angezeigt wird, ist der Bildschirm "Mitgliederliste". Wir werden hier auch einen "Einladen" -Button einrichten. Das Benutzerszenario ist wie folgt.

  1. Drücken Sie für jede Aufführung im Bildschirm "Mitgliederliste" auf "Einladen".
  2. Die Ansicht (oben erstellt) zum Erstellen der Einladung wird angezeigt.
  3. Senden Sie das Formular, indem Sie die ID des Benutzers eingeben, den Sie einladen.
  4. Sie können zum Bildschirm "Mitgliederliste" zurückkehren und die Einladung überprüfen, die Sie gemacht haben.

Erstellen Sie eine Ansicht

views.py


from django.views.generic import ListView
from django.core.exceptions import PermissionDenied


class UsrList(LoginRequiredMixin, ListView):
    '''ProdUser-Listenansicht
    '''
    model = ProdUser
    
    def get(self, request, *args, **kwargs):
        '''Handler, um die Anforderung zum Zeitpunkt der Anzeige zu erhalten
        '''
        #Erhalten Sie Leistungsbenutzer Zugriffsinformationen und überprüfen Sie die Zugriffsrechte
        prod_user = accessing_prod_user(self, kwargs['prod_id'])
        if not prod_user:
            raise PermissionDenied
        
        #Machen Sie es zu einem Ansichtsattribut, damit auf es aus der Vorlage verwiesen werden kann
        self.prod_user = prod_user
        
        #Machen Sie es zu einem Attribut der Ansicht, um die eingeladenen Benutzer anzuzeigen
        self.invitations = Invitation.objects.filter(production=prod_user.production)
        
        return super().get(request, *args, **kwargs)
    
    #Folgendes wird weggelassen

―― Ursprünglich handelt es sich um eine Ansicht, in der eine Liste der "Leistungsbenutzer" angezeigt wird. Um jedoch eine Liste der Einladungen auf demselben Bildschirm anzuzeigen, werden die Einladungen auf Ansichtsattribute ("Einladungen") festgelegt, damit auf sie aus der Vorlage verwiesen werden kann. ――Im vorherigen Abschnitt habe ich "Einladungskarte, die ich gemacht habe" geschrieben, aber genau genommen habe ich "Einladungskarte, die zu dieser Aufführung einlädt" erhalten. Der Grund ist, dass es angezeigt wird, auch wenn sich der Eigentümer der "Leistung" ändert.

Erstellen Sie eine Vorlage

Es wird nur der Teil extrahiert, der die Einladung anzeigt. Um das Ganze zu sehen, gehen Sie zu Code on GitHub.

produser_list.html


{% if view.prod_user.is_owner and view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>Benutzer einladen</strong></p>
<table>
    <tr>
        <th>Benutzeridentifikation</th>
        <th>Nachname</th>
        <th>Name</th>
        <th>Einladungsfrist</th>
    </tr>
    {% for item in view.invitations %}
    <tr>
        <td>{{ item.invitee.username }}</td>
        <td>{{ item.invitee.last_name }}</td>
        <td>{{ item.invitee.first_name }}</td>
        {% if item.expired %}
            <td style="color:red;">{{ item.exp_dt }}</td>
        {% else %}
            <td>{{ item.exp_dt }}</td>
        {% endif %}
        <td><a href="{% url 'production:invt_delete' pk=item.id from='usr_list' %}" class="deletelink">Löschen</a></td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}

Mechanismus zum Anzeigen empfangener Einladungen

Der Bildschirm, auf dem die Einladung angezeigt wird, die Sie erhalten haben, ist der Bildschirm "Teilnehmende Aufführungen". Ursprünglich ist es ein Bildschirm, auf dem eine Liste der teilnehmenden Aufführungen angezeigt wird. Genau wie beim Anzeigen der erstellten Einladung wird der Bereich für die Einladung jedoch nur angezeigt, wenn die Bedingungen erfüllt sind.

Erstellen Sie eine Ansicht

views.py


class ProdList(LoginRequiredMixin, ListView):
    '''Produktionslistenansicht
    
Das Modell ist ProdUser, da nur die Leistungen des angemeldeten Benutzers angezeigt werden.
    '''
    model = ProdUser
    template_name = 'production/production_list.html'
    
    def get(self, request, *args, **kwargs):
        '''Handler, um die Anforderung zum Zeitpunkt der Anzeige zu erhalten
        '''
        #Machen Sie es zu einem Ansichtsattribut, um Einladungen an die Truppe anzuzeigen
        now = datetime.now(timezone.utc)
        self.invitations = Invitation.objects.filter(invitee=self.request.user,
            exp_dt__gt=now)
        
        return super().get(request, *args, **kwargs)
    
    #Folgendes wird weggelassen

Erstellen Sie eine Vorlage

Es wird nur der Teil extrahiert, der die Einladung anzeigt. Um das Ganze zu sehen, gehen Sie zu Code auf GitHub.

production_list.html


{% if view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>Eingeladene Gruppe</strong></p>
<table>
    <tr>
        <th>Leistungsname</th>
        <th>Einladungs-ID</th>
        <th>Einladungsfrist</th>
    </tr>
    {% for item in view.invitations %}
    <tr>
        <td>{{ item.production }}</td>
        <td>{{ item.inviter.username }}</td>
        {% if item.expired %}
            <td style="color:red;">{{ item.exp_dt }}</td>
        {% else %}
            <td>{{ item.exp_dt }}</td>
        {% endif %}
        <td>
            <a href="{% url 'production:prod_join' invt_id=item.id %}" class="addlink">Beteiligung</a>
            <a href="{% url 'production:invt_delete' pk=item.id from='prod_list' %}" class="deletelink">Löschen</a>
        </td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}

Mechanismus zum Löschen von Einladungen

Beim Anzeigen der von mir erstellten und erhaltenen Einladungen habe ich in der Vorlage die Schaltfläche "Löschen" eingefügt. Erstellen Sie eine Ansicht, auf die Sie über diesen Link zugreifen können (product: invoke_delete).

Erstellen Sie eine Ansicht

views.py


from django.views.generic.edit import DeleteView


class InvtDelete(LoginRequiredMixin, DeleteView):
    '''Einladungs-Löschansicht
    '''
    model = Invitation
    template_name_suffix = '_delete'

-- template_name_suffix scheint standardmäßig'_confirm_delete 'zu sein. Dieser Bereich ist Ihre Wahl.

views.py


    def get(self, request, *args, **kwargs):
        '''Handler, um die Anforderung zum Zeitpunkt der Anzeige zu erhalten
        '''
        #Überprüfen Sie, ob Sie der Eigentümer der Aufführung oder der eingeladene Teilnehmer der Einladung sind
        invt = self.get_object()
        prod_id = invt.production.id
        prod_user = accessing_prod_user(self, prod_id)
        
        is_owner = prod_user and prod_user.is_owner
        is_invitee = self.request.user == invt.invitee
        
        if not (is_owner or is_invitee):
            raise PermissionDenied
        
        return super().get(request, *args, **kwargs)

views.py


    def post(self, request, *args, **kwargs):
        '''Handler, um die Anfrage beim Speichern zu erhalten
        '''
        #Überprüfen Sie, ob Sie der Eigentümer der Aufführung oder der eingeladene Teilnehmer der Einladung sind
        invt = self.get_object()
        prod_id = invt.production.id
        prod_user = accessing_prod_user(self, prod_id)
        
        is_owner = prod_user and prod_user.is_owner
        is_invitee = self.request.user == invt.invitee
        
        if not (is_owner or is_invitee):
            raise PermissionDenied
        
        return super().post(request, *args, **kwargs)

--Das gleiche, was der POST-Handler tut, ist dasselbe wie GET. Wenn Sie sich beim Duplizieren unwohl fühlen, können Sie es zu einer Methode machen.

views.py


    def get_success_url(self):
        '''Geben Sie das Übergangsziel dynamisch an, wenn das Löschen erfolgreich ist
        '''
        if self.kwargs['from'] == 'usr_list':
            prod_id = self.object.production.id
            url = reverse_lazy('production:usr_list', kwargs={'prod_id': prod_id})
        else:
            url = reverse_lazy('production:prod_list')
        return url
    
    def delete(self, request, *args, **kwargs):
        '''Nachricht beim Löschen
        '''
        result = super().delete(request, *args, **kwargs)
        messages.success(
            self.request, str(self.object) + "Wurde gelöscht.")
        return result

Erstellen Sie eine Vorlage

Es ist eine einfache Seite mit nur einer Schaltfläche "Löschen" und einer Schaltfläche "Abbrechen".

invitation_delete.html


{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
{% if view.kwargs.from == 'usr_list' %}
<a href="{% url 'production:usr_list' prod_id=object.production.id %}">◀</a>
{% else %}
<a href="{% url 'production:prod_list' %}">◀</a>
{% endif %}
Einladung zur Truppe entfernen
</h1>

<form method="post">{% csrf_token %}
    <p>Möchten Sie diese Einladung löschen?</p>
    <input type="submit" value="löschen">
    <input type="button"
        {% if view.kwargs.from == 'usr_list' %}
        onclick="location.href='{% url 'production:usr_list' prod_id=object.production.id %}'"
        {% else %}
        onclick="location.href='{% url 'production:prod_list' %}'"
        {% endif %}
        value="Stornieren">
</form>

<table>
    <tr><th>Performance</th><td>{{ object.production }}</td></tr>
    <tr><th>ID der eingeladenen Person</th><td>{{ object.invitee.username }}</td></tr>
    <tr><th>Nachname</th><td>{{ object.invitee.last_name }}</td></tr>
    <tr><th>Name</th><td>{{ object.invitee.first_name }}</td></tr>
    <tr><th>Frist</th>
    {% if object.expired %}
        <td style="color:red;">{{ object.exp_dt }}</td>
    {% else %}
        <td>{{ object.exp_dt }}</td>
    {% endif %}
    </tr>
</table>
{% endblock %}

Mechanismus zur Teilnahme an der Aufführung aus der Einladung

Erstellen Sie eine Ansicht

Bei Auswahl von "Verbinden" wird ein neuer "Leistungsbenutzer" erstellt. Erstellen Sie daher die folgende "CreateView". Der Name der Vorlage lautet jedoch eindeutig "product_join.html".

views.py


from django.http import Http404


class ProdJoin(LoginRequiredMixin, CreateView):
    '''Anzeigen, um an der Aufführung teilzunehmen
    '''
    model = ProdUser
    fields = ('production', 'user')
    template_name = 'production/production_join.html'
    success_url = reverse_lazy('production:prod_list')

--Die in "Felder" angegebenen "Produktion" und "Benutzer" werden nicht vom Benutzer eingegeben, sondern ausgeblendet und in die Vorlage eingebettet, damit sie von "Formular" empfangen werden können. ―― Natürlich können Sie es mit dem Code festlegen, wie Sie es in ["Mechanismus zum Erstellen von Einladungen"](#Mechanismus zum Erstellen von Einladungen) getan haben.

views.py


    def get(self, request, *args, **kwargs):
        '''Handler, um die Anforderung zum Zeitpunkt der Anzeige zu erhalten
        '''
        #Überprüfen Sie, ob Sie eingeladen sind, und erhalten Sie eine Aufführung, an der Sie teilnehmen können
        self.production = self.production_to_join()
        
        return super().get(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        '''Handler, um die Anfrage beim Speichern zu erhalten
        '''
        #Überprüfen Sie, ob Sie eingeladen sind, und erhalten Sie eine Aufführung, an der Sie teilnehmen können
        self.production = self.production_to_join()
        
        return super().post(request, *args, **kwargs)

views.py


    def production_to_join(self):
        '''Überprüfen Sie, ob Sie eingeladen sind, und geben Sie eine Aufführung zurück, an der Sie teilnehmen können
        '''
        #Wirf einen 404-Fehler aus, wenn du nicht eingeladen wirst
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            raise Http404
        invt = invts[0]
        
        #Wirf einen 404-Fehler aus, wenn die Einladung abgelaufen ist
        now = datetime.now(timezone.utc)
        if now > invt.exp_dt:
            raise Http404
        
        #PermissionDenied, wenn der zugreifende Benutzer nicht eingeladen ist
        user = self.request.user
        if user != invt.invitee:
            raise PermissionDenied
        
        return invt.production

――Es ist besser, eine Nachricht über das Ablaufdatum anzuzeigen, aber hier wird einfach 404 zurückgegeben.

views.py


    def form_valid(self, form):
        '''Nach bestandener Validierung
        '''
        #Einladung prüfen
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            return self.form_invalid(form)
        invt = invts[0]
        
        #Holen Sie sich den Datensatz zum Speichern
        new_prod_user = form.save(commit=False)
        
        #Ist die richtige Leistung eingestellt?
        if new_prod_user.production != invt.production:
            return self.form_invalid(form)
        
        #Ist der richtige Benutzer eingestellt?
        if new_prod_user.user != invt.invitee:
            return self.form_invalid(form)
        
        #Einladung löschen
        invt.delete()
        
        messages.success(self.request, str(invt.production) + "Ich nahm teil an.")
        return super().form_valid(form)
    
    def form_invalid(self, form):
        '''Wenn Sie nicht teilnehmen
        '''
        messages.warning(self.request, "Ich konnte nicht teilnehmen.")
        return super().form_invalid(form)

Erstellen Sie eine Vorlage

Es ist eine einfache Seite mit nur einer Schaltfläche "Beitreten" und einer Schaltfläche "Später".

production_join.html


{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
<a href="{% url 'production:prod_list' %}">◀</a>
Nehmen Sie an der Aufführung teil
</h1>

<div>&nbsp;</div>

<p>
{{ view.production }}Wurden eingeladen zu. Sind Sie sicher, dass Sie teilnehmen möchten?
</p>

<form method="post">
    {% csrf_token %}
    <input type="hidden" name="production" value="{{ view.production.id }}">
    <input type="hidden" name="user" value="{{ view.request.user.id }}">
    <input type="submit" value="Beteiligung">
    <input type="button"
        onclick="location.href='{% url 'production:prod_list' %}'"
        value="später">
</form>
{% endblock %}

Zusammenfassung

Wir haben die Implementierung der Funktion eingeführt, um Benutzer zu einer "Projekteinheit" (Gruppe) namens "Leistung" einzuladen und den eingeladenen Benutzern die Teilnahme daran zu ermöglichen. Die verwendeten Techniken sind nachstehend zusammengefasst.

Recommended Posts

Teilen Sie Daten mit Django (3) in projektähnliche Einheiten.
Teilen Sie Daten mit Django in projektähnliche Einheiten
Teilen Sie Daten mit Django (2) in projektähnliche Einheiten.
Verwenden Sie Django, um Tweet-Daten zu speichern
Verwalten Sie Ihre Daten mit AWS RDS
Teilen Sie Japanisch (Katakana) in Silben [Python]
Erstellen der ersten App mit Django Startprojekt
Machen Sie Urlaubsdaten mit Pandas zu einem Datenrahmen
Trainieren Sie Stanford NER Tagger mit Ihren eigenen Daten
Internationalisierung mit Django
CRUD mit Django
Versuchen Sie, Twitter-Daten in SPAM und HAM zu unterteilen
Einmaliges Anmelden bei der Django-Anwendung mit AWS SSO
Teilen Sie den Datensatz (ndarray) mit NumPy in beliebige Proportionen