~widelands-dev/widelands-website/trunk

« back to all changes in this revision

Viewing changes to wlmaps/forms.py

  • Committer: kaputtnik
  • Date: 2019-09-03 06:16:23 UTC
  • mfrom: (544.2.25 pybb_attachments)
  • Revision ID: kaputtnik-20190903061623-xu4kvqpabnzmuskw
Allow file uploads in the forum; added some basic file checks to validate files

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
 
7
7
from django import forms
8
8
from django.forms import ModelForm
 
9
from django.conf import settings
9
10
from django.core.files.storage import default_storage
10
 
from django.conf import settings
11
11
 
12
12
from wlmaps.models import Map
13
13
import os
14
14
import shutil
 
15
import tempfile
15
16
 
16
17
 
17
18
class UploadMapForm(ModelForm):
18
19
    """
 
20
    All file operations are done in the temp folder.
 
21
    
19
22
    We have to handle here three different kind of files:
20
 
    1. The map which is uploaded
21
 
    2. The files created by 'wl_map_info'
22
 
        a. The json file containing infos of the map
23
 
        b. The image of the minimap (png)
24
 
 
25
 
    The filename of uploaded maps may contain bad characters which must be handled.
26
 
 
27
 
    If a map get deleted in the database, the underlying files (.wmf, .png) still exists. Uploading
28
 
    a map with the same name does not overwrite the existing file(s), instead they get a
29
 
    name in form of 'filename_<random_chars>.wmf(.png)'. This is importand for linking the correct
30
 
    minimap.
31
 
    The map file and the minimap (png) have different random characters in such a case, because the map
32
 
    get saved twice: One time for the call to wl_map_info, and when the model is saved.
 
23
    1. The map which is uploaded, stored as e.g '/tmp/tmp_xyz.upload'.
 
24
       Because 'wl_map_info' can't handle files with this extension and
 
25
       produces a minimap with the same name like the initial name, the
 
26
       uploade file will be copied to a file with the name of the
 
27
       uploaded file and extension, e.g. '/tmp/original_filename.wmf'
 
28
    2. 'wl_map_info' produces two files which will be stored at the same
 
29
        location as the uploaded file and named like the map file:
 
30
        a. '/tmp/original_filename.wmf.json': Contains infos of the map
 
31
        b. '/tmp/original_filename.wmf.png': The image of the minimap (png).
 
32
 
 
33
    Because we can't be sure the original filename is a valid filename, we
 
34
    may modify it to be a valid filename. 
33
35
    """
34
36
 
35
37
    class Meta:
39
41
    def clean(self):
40
42
        cleaned_data = super(UploadMapForm, self).clean()
41
43
 
42
 
        mem_file_obj = cleaned_data.get('file')
43
 
        if not mem_file_obj:
 
44
        file_obj = cleaned_data.get('file')
 
45
        if not file_obj:
44
46
            # no clean file => abort
45
47
            return cleaned_data
46
48
 
47
 
        try:
48
 
            # Try to make a safe filename
49
 
            safe_name = default_storage.get_valid_name(mem_file_obj.name)
50
 
            file_path = settings.MEDIA_ROOT + 'wlmaps/maps/' + safe_name
51
 
            saved_file = default_storage.save(file_path, mem_file_obj)
52
 
        except UnicodeEncodeError:
53
 
            self._errors['file'] = self.error_class(
54
 
                ['The filename contains characters which cannot be handled. Please rename and upload again.'])
55
 
            del cleaned_data['file']
56
 
            return cleaned_data
 
49
        # Make a save filename and copy the uploaded file
 
50
        saved_file = file_obj.temporary_file_path()
 
51
        safe_name = default_storage.get_valid_name(file_obj.name)
 
52
        tmpdir = tempfile.gettempdir()
 
53
        copied_file = shutil.copyfile(saved_file,
 
54
                                      os.path.join(tmpdir, safe_name)
 
55
                                      )
57
56
 
58
57
        try:
59
58
            # call map info tool to generate minimap and json info file
 
59
            # change working directory so that the datadir is found
60
60
            old_cwd = os.getcwd()
61
61
            os.chdir(settings.WIDELANDS_SVN_DIR)
62
 
            check_call(['wl_map_info', saved_file])
63
 
 
64
 
            # Deleting the file because it will be saved again when
65
 
            # the model is saved.
66
 
            default_storage.delete(saved_file)
 
62
            check_call(['wl_map_info', copied_file])
67
63
            os.chdir(old_cwd)
68
64
        except CalledProcessError:
69
65
            self._errors['file'] = self.error_class(
70
66
                ['The map file could not be processed.'])
71
67
            del cleaned_data['file']
 
68
            os.remove(copied_file)
72
69
            return cleaned_data
73
70
 
74
 
        mapinfo = json.load(open(saved_file + '.json'))
 
71
        mapinfo = json.load(open(copied_file + '.json'))
75
72
 
76
73
        if Map.objects.filter(name=mapinfo['name']):
77
74
            self._errors['file'] = self.error_class(
78
75
                ['A map with the same name already exists.'])
79
76
            del cleaned_data['file']
 
77
            # Delete the file copy and the generated files
 
78
            try:
 
79
                os.remove(copied_file)
 
80
                os.remove(copied_file + '.json')
 
81
                os.remove(copied_file + '.png')
 
82
            except os.FileNotFoundError:
 
83
                pass
 
84
 
80
85
            return cleaned_data
81
86
 
82
87
        # Add information to the map
90
95
        self.instance.world_name = mapinfo['world_name']
91
96
        self.instance.wl_version_after = mapinfo['needs_widelands_version_after']
92
97
 
93
 
        # mapinfo["minimap"] is the absolute path containing the path where it
94
 
        # is saved, extract the name
 
98
        # mapinfo["minimap"] is the absolute path to the image file
 
99
        # We move the file to the correct destination
95
100
        minimap_name = mapinfo['minimap'].rpartition('/')[2]
96
101
        minimap_upload_to = self.instance._meta.get_field('minimap').upload_to
97
 
        # Set the destination relative to MEDIA_ROOT
98
102
        minimap_path = os.path.join(minimap_upload_to, minimap_name)
99
103
        self.instance.minimap = minimap_path
100
 
        # Move the minimap file (.png) from wlmaps/maps to wlmaps/minimaps
101
104
        shutil.move(mapinfo['minimap'], os.path.join(
102
105
            settings.MEDIA_ROOT, minimap_path))
103
106
 
104
 
        # the json file is no longer needed
105
 
        default_storage.delete(saved_file + '.json')
 
107
        # Final cleanup
 
108
        os.remove(copied_file + '.json')
 
109
        os.remove(copied_file)
106
110
 
107
111
        return cleaned_data
108
112