Repository: pretix/pretix

Issue:
Allow to copy products
**Hey!** If you ever wanted to contribute to an Open Source project, but didn't know how, you just found a perfect place to start. We try to write this issue as instructive as possible, but as we're humans we almost certainly forgot to write something important in here. If you want to try to tackle this issue and get stuck at _any_ point, please do not hesitate and ask us, either here on GitHub or via email at support@pretix.eu. We'll be very happy to help! 

We think this issue is very suitable to people who are just starting with Python or Django development or who never contributed to Open Source before. We'd like to ask people with wide experience in these areas to leave this issue to beginners. Thanks! (If you want something more advanced, just drop us a line, we'll find something :wink:)

## How to get started

First of all, you need pretix running locally on your machine. We have a guide on the developer installation [over here](https://docs.pretix.eu/en/latest/development/setup.html). Play around with it and get a feeling for what pretix is and how it works :) Try to find the places in the interface mentioned in this issue where something needs to change. Maybe try to find the same places in the code? Just searching for characteristic words can be a powerful tool to find your way around.

## What this is about

If you want to sell tickets with pretix, you first need to create one or more so-called "products". Most of the time, you will need more than one of them (because you have multiple prices etc.) and most of the time, they would be very similar. To save time, it would be useful to be able to copy over all configuration from one product to another.

## How to do this

If you are not familiar with Django, you should first take a quick look at Django's documentation on models and forms to understand broadly what they are about. You don't need to know every detail :)

Heads up: Internally, products are often called items. Sorry for that ;)

To get started, you should extend the ``ItemCreateForm`` in ``pretix/control/forms/items.py`` with a new field that allows you to select a product to copy from (or none). You can take inspiration from ``EventWizardCopyForm`` in ``pretix/control/forms/event.py``. You then need to add the form field to the ``pretix/control/templates/pretixcontrol/item/create.html`` template.

As a final step, you will need to add some code to ``ItemCreate.form_valid`` in ``pretix/control/views/item.py`` to actually copy over the settings. This will basically be a block of code that sets the property of the new item to the value of the old one, e.g.:

```
if form.cleaned_data['copy_from']:
    form.instance.available_until = form.cleaned_data['copy_from'].available_until
    ...
```

## I want to give it a try!

You're still reading, nice! If you want to try working on this, please comment below, so everyone else knows this is being looked at and can search for a different place to contribute. 

First, [fork](https://github.com/pretix/pretix/fork) the repository to your own GitHub account. Then clone it locally (or [set it as your remote](https://help.github.com/articles/configuring-a-remote-for-a-fork/) if you already cloned it earlier) and prepare your pull requests. There are a number of tutorials on the Pull Request workflow online, for example this [short version](https://guides.github.com/introduction/flow/) by GitHub itself and this [more in-depth tutorial](https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github). We're excited to see your pull request! 

And don't forget: If you get stuck, don't hestitate to ask. We bound ourselves to a [Code of Conduct](https://docs.pretix.eu/en/latest/development/contribution/codeofconduct.html) and we're committed to providing a welcoming experience and giving constructive, friendly and useful feedback on your contributions.


Discussion:
new to contributing to open source projects, but I'd love to give this one a try
Sure, go ahead! If you have any questions, just ask :)
When choosing which product to copy information from, where should this list of products come from? I'm modeling this after EventWizardCopyForm and am stuck on what queryset to use
Something like ``event.items.all()`` should be fine :) You might need to add something to the constructor of the form class to pass the event from the view into the form.

Resolving patch:
diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py
--- a/src/pretix/control/forms/item.py
+++ b/src/pretix/control/forms/item.py
@@ -105,12 +105,32 @@ class ItemCreateForm(I18nModelForm):
                                                     'You can select the variations in the next step.'),
                                         required=False)
 
+    def __init__(self, *args, **kwargs):
+        self.event = kwargs['event']
+        super().__init__(*args, **kwargs)
+        self.fields['copy_from'] = forms.ModelChoiceField(
+            label=_("Copy product information"),
+            queryset=self.event.items.all(),
+            widget=forms.Select,
+            empty_label=_('Do not copy'),
+            required=False
+        )
+
     def save(self, *args, **kwargs):
         instance = super().save(*args, **kwargs)
         if self.cleaned_data.get('has_variations'):
-            ItemVariation.objects.create(
-                item=instance, value=__('Standard')
-            )
+            if self.cleaned_data.get('copy_from') and self.cleaned_data.get('copy_from').has_variations:
+                for variation in self.cleaned_data['copy_from'].variations.all():
+                    ItemVariation.objects.create(item=instance, value=variation.value, active=variation.active,
+                                                 position=variation.position, default_price=variation.default_price)
+            else:
+                ItemVariation.objects.create(
+                    item=instance, value=__('Standard')
+                )
+
+        for question in Question.objects.filter(items=self.cleaned_data.get('copy_from')):
+            question.items.add(instance)
+
         return instance
 
     class Meta:
diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py
--- a/src/pretix/control/views/item.py
+++ b/src/pretix/control/views/item.py
@@ -766,6 +766,16 @@ def get_success_url(self) -> str:
     @transaction.atomic
     def form_valid(self, form):
         messages.success(self.request, _('Your changes have been saved.'))
+        if form.cleaned_data['copy_from']:
+            form.instance.category = form.cleaned_data['copy_from'].category
+            form.instance.description = form.cleaned_data['copy_from'].description
+            form.instance.active = form.cleaned_data['copy_from'].active
+            form.instance.available_from = form.cleaned_data['copy_from'].available_from
+            form.instance.available_until = form.cleaned_data['copy_from'].available_until
+            form.instance.require_voucher = form.cleaned_data['copy_from'].require_voucher
+            form.instance.hide_without_voucher = form.cleaned_data['copy_from'].hide_without_voucher
+            form.instance.allow_cancel = form.cleaned_data['copy_from'].allow_cancel
+
         ret = super().form_valid(form)
         form.instance.log_action('pretix.event.item.added', user=self.request.user, data={
             k: (form.cleaned_data.get(k).name


Test patch: