2

I am trying to build an application which takes different CSV files with a different number of columns. For example, my CSV file has around 30 columns whose field names have special characters. So, I would like to update the field names given by the user.I have a CSV file that looks something like this:

ques.1,ques.2
3,5
5,1

I want to update the column names ques.1, ques.2 with the titles given by the user (TV, Radio).

Python:

def upload():
    if request.method == 'POST':
        # check if the post request has the file part
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']

        # if user does not select file, browser also
        # submit a empty part without filename
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)

        if file and allowed_file(file.filename):
            app.logger.info('File Saved')

            filename = secure_filename(file.filename)
            savepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            flash(savepath)
            file.save(savepath)

            save_in(savepath)

            return redirect(url_for('upload', filename=filename))

    return render_template('pages/placeholder.upload.html') 

def save_in(savepath):
    app.logger.info(savepath)

    csv_rows = []

    with open(savepath) as csvfile:
        reader = csv.DictReader(csvfile)
        title = reader.fieldnames
        for row in reader:
            csv_rows.extend([{title[i]:row[title[i]] for i in range(len(title))}])

I have tried to populate the field names in a selection menu. But I am not able to figure out how to make the user:

  1. Choose the field-name to update from the selection menu.
  2. Enter the new field name.
  3. Click on the "update" button to change the field name.

HTML

<div class="container">
    <form class="col s12" action="/upload" method="POST" enctype="multipart/form-data">
    <div class="row">
        <div class="input-field col s12 m6">
             <label>Update Fieldnames</label>
                <select class="icons">
                {% for x in title %}
                <option value="{{ x }}"{% if loop.first %} SELECTED{% endif %}>{{ x }}</option>
                {% endfor %}
                </select>
                <button class="btn" type="submit" value="Update">Submit</button>
        </div>
    </div>
    </form>
</div>
PRMoureu
  • 12,817
  • 6
  • 38
  • 48
gkuhu
  • 299
  • 1
  • 4
  • 17
  • I think you have better options to draw a label (field name) and a text input (new field name), instead of the select part, it will be easier to handle, and its also more handy for users – PRMoureu Sep 06 '17 at 21:20
  • I will be using different CSV files with a different number of columns, therefore, I am not sure how many labels and text inputs will be required. I thought giving a selection menu would be a better idea and update it every time. – gkuhu Sep 06 '17 at 21:55
  • Inside the `for` loop you can draw one label and one input in a line, exactly like you did for one option of the selection menu. Maybe you had an easier idea to get the new title from user ? – PRMoureu Sep 06 '17 at 22:12
  • Could you show me an example to how to do it and after getting the text input how to handle it Flask? – gkuhu Sep 07 '17 at 02:51
  • Hi @PRMoureu I could not try out the code you posted earlier due to some health issues. If will be grateful if you could repost the code? – gkuhu Sep 20 '17 at 17:18
  • Thanks, I will just try and let you know. – gkuhu Sep 20 '17 at 17:41

1 Answers1

1

The Template

As said in the comments previously, you should replace the selection menu by a list of label and text input (in the html template). This makes the process much easier for you and mostly for users, they can update all fields instead of editing them one by one.

The text input can be identified by a name containing the loop index, so you can match the original field in the POST request (even if two fieldnames are equal). The default value is the original title, so user can keep it or modify.

Display Example:

Field 1 : Title1
Field 2 : Title2

<div class="container">
    <form class="col s12" action="/update" method="POST" enctype="multipart/form-data">
        <div class="row">
            <h2>Update Fieldnames</h2>
                {% for x in title %}
                  <label> Field {{ loop.index }} : </label>
                  <input type="text" name="field-{{ loop.index }}" value="{{ x }}" ></input>
                  <br>
                {% endfor %}
            <input type="hidden" name="filename" value= {{filename}}></input>
            <button class="btn" type="submit" value="Update">Submit</button>
        </div>
    </form>
</div>

The filename is added as hidden input in the form to be able to reach the file in the view, and handle the fields update.

This requires to add the variable in the render_template statement :

return render_template('main.html', title=data, filename=savepath) #according to the variable in the function save_in()

The View

Then the update view can grab the request.form values and create a new list of fieldnames.

@app.route("/update", methods=['POST'])
def update():
    filepath = request.form['filename']
    newtitles = []
    for index in range(1, len(request.form)):  # begin at 1 and leave filename out
        newtitles.append(request.form.get('field-' + str(index)))

    rows = [newtitles] # stores the new header

    with open(filepath, 'r') as csv_in :
        reader = csv.reader(csv_in)
        next(reader, None) # ignores the original header
        for row in reader:
            rows.append(row) # stores all rows

    with open(filepath, 'w') as csv_out:
        writer = csv.writer(csv_out)
        writer.writerows(rows) # writes all rows, including new header

    return """<html><div>New titles updated: {} to the file {}</div></html>""".format(newtitles, filepath)

The file is replaced by a new version, it takes some lines more to achieve this, but if you prefer keeping the original version, you can get some inspiration from this post.

PRMoureu
  • 12,817
  • 6
  • 38
  • 48
  • return render_template('main.html', title=data, filename=savepath) – gkuhu Oct 11 '17 at 05:57
  • Thanks. It has been very useful for me.'for index in range(1, len(request.form)): newtitles.append(request.form.get('field-' + str(index)))' These two lines did not work for me, so i found out the number of fields-- `for index in range(1, numofField+1): newtitles.append(request.form['field-' + str(index)])` – gkuhu Oct 20 '17 at 02:11
  • How to apply form validation to the names i enter. I want the length of the names of to be of size 8 with no special characters? – gkuhu Oct 26 '17 at 01:24
  • 1
    Hi, you can test the function from this [post](https://stackoverflow.com/questions/24774367/how-to-validate-html-textbox-not-to-allow-special-characters-and-space) for the special characters, and apply `maxlength="8"` inside the tag ` – PRMoureu Oct 26 '17 at 06:04
  • Note that this only works with keypress, you may need to check this in the view instead if you want to prevent copypasted forbidden characters. Next step could be to use WTF forms with validators – PRMoureu Oct 26 '17 at 06:35